MIPESCA_test / app.py
Edoruin's picture
ajustando codigo a la estructura de la API
810acca
import os
import csv
import json
import requests
import re
from datetime import datetime, timedelta
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
from models import Capture, Species, ClosedSeason
app = Flask(__name__, static_folder='static', static_url_path='')
CORS(app)
# Cache for FishWatch API data
_fishwatch_cache = {
'data': None,
'expiry': None
}
def fetch_fishwatch_data(retries=2):
"""Fetch species data from FishWatch API with caching and retries."""
global _fishwatch_cache
if _fishwatch_cache['data'] and _fishwatch_cache['expiry'] > datetime.now():
return _fishwatch_cache['data']
for attempt in range(retries):
try:
# Note: FishWatch API can be slow or unstable
url = "https://www.fishwatch.gov/api/species"
print(f"DEBUG: Fetching FishWatch data (attempt {attempt + 1}/{retries})...")
# Using allow_redirects=True (default) but being explicit
response = requests.get(url, timeout=30, allow_redirects=True)
if response.status_code == 200:
text = response.text.strip()
if not text:
print("DEBUG: FishWatch API returned empty response")
continue
# Robust JSON parsing
try:
data = json.loads(text) if hasattr(requests, 'json') else response.json()
_fishwatch_cache['data'] = data
_fishwatch_cache['expiry'] = datetime.now() + timedelta(hours=2)
print(f"DEBUG: Successfully fetched {len(data)} species from FishWatch")
return data
except Exception as e:
print(f"DEBUG: JSON decode error on FishWatch: {e}")
print(f"DEBUG: Response starts with: {text[:100]}")
else:
print(f"DEBUG: FishWatch API returned status: {response.status_code}")
except requests.exceptions.Timeout:
print(f"DEBUG: FishWatch API timeout (attempt {attempt + 1}/{retries})")
except Exception as e:
print(f"DEBUG: Error fetching FishWatch data: {e}")
return []
def slugify(text):
"""Simple slugify for species IDs."""
text = text.lower()
text = re.sub(r'[^\w\s-]', '', text)
return re.sub(r'[-\s]+', '-', text).strip('-')
def load_species():
"""Load unique species from the provided CSV file enriched with FishWatch API."""
species_list = []
seen_products = set()
# Ensure correct CSV path
base_dir = os.path.dirname(os.path.abspath(__file__))
csv_path = os.path.join(base_dir, 'produccion_pesca_limpia_refined.csv')
print(f"DEBUG: CWD is {os.getcwd()}")
print(f"DEBUG: base_dir is {base_dir}")
print(f"DEBUG: Checking CSV path: {csv_path}")
print(f"DEBUG: Files in base_dir: {os.listdir(base_dir)}")
# Fetch FishWatch data
fishwatch_data = fetch_fishwatch_data()
# Map by common name (normalized) for easier lookup
fw_map = {fw.get('Species Name', '').lower(): fw for fw in fishwatch_data}
# Pre-defined base species (with more metadata if available)
base_metadata = {
'chillo': {'sci': 'Lutjanus campechanus', 'img': 'assets/species/chillo.jpg', 'veda': ClosedSeason(start='04-01', end='06-30', description='Veda de reproducción')},
'dorado': {'sci': 'Coryphaena hippurus', 'img': 'assets/species/dorado.jpg'},
'langosta-comun-del-caribe': {'sci': 'Panulirus argus', 'img': 'assets/species/langosta.jpg', 'veda': ClosedSeason(start='03-01', end='06-30', description='Veda de reproducción')}
}
if os.path.exists(csv_path):
with open(csv_path, mode='r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
product_name = row['Producto']
category = row['categoria']
if product_name not in seen_products:
species_id = slugify(product_name)
metadata = base_metadata.get(species_id, {})
# Try to find in FishWatch
fw_info = fw_map.get(product_name.lower())
img_url = metadata.get('img', 'assets/species/placeholder.jpg')
sci_name = metadata.get('sci', '')
if fw_info:
# Extract illustration if available
photos = fw_info.get('Species Illustration Photo', {})
if isinstance(photos, dict) and photos.get('src'):
img_url = photos['src']
elif isinstance(photos, list) and len(photos) > 0:
img_url = photos[0].get('src', img_url)
if not sci_name:
sci_name = fw_info.get('Scientific Name', '')
species_list.append(Species(
id=species_id,
commonName=product_name,
scientificName=sci_name,
category=category,
imageUrl=img_url,
protected=False,
closedSeason=metadata.get('veda')
))
seen_products.add(product_name)
# If CSV failed or is empty, use defaults
if not species_list:
print("CSV not found or empty, using expanded fallback list")
species_list = [
Species(id='chillo', commonName='Chillo', scientificName='Lutjanus campechanus', category='Peces', imageUrl='assets/species/chillo.jpg', protected=False, closedSeason=ClosedSeason(start='04-01', end='06-30', description='Veda de reproducción')),
Species(id='dorado', commonName='Dorado', scientificName='Coryphaena hippurus', category='Peces', imageUrl='assets/species/dorado.jpg', protected=False),
Species(id='langosta', commonName='Langosta del Caribe', scientificName='Panulirus argus', category='Crustáceos', imageUrl='assets/species/langosta.jpg', protected=False, closedSeason=ClosedSeason(start='03-01', end='06-30', description='Veda de reproducción')),
Species(id='mero', commonName='Mero', scientificName='Epinephelus itajara', category='Peces', imageUrl='assets/species/placeholder.jpg', protected=False),
Species(id='atun', commonName='Atún', scientificName='Thunnus', category='Peces', imageUrl='assets/species/placeholder.jpg', protected=False),
Species(id='pulpo', commonName='Pulpo', scientificName='Octopus vulgaris', category='Moluscos', imageUrl='assets/species/placeholder.jpg', protected=False),
Species(id='lambí', commonName='Lambí', scientificName='Lobatus gigas', category='Moluscos', imageUrl='assets/species/placeholder.jpg', protected=False),
]
return species_list
# Load Data
SPECIES_DATA = load_species()
captures_storage = []
@app.route('/')
def index():
return app.send_static_file('index.html')
@app.route('/health')
def health_check():
return jsonify({
'status': 'healthy',
'timestamp': datetime.now().isoformat()
})
@app.route('/api/species', methods=['GET'])
def get_species():
return jsonify([s.to_dict() for s in SPECIES_DATA])
@app.route('/api/captures', methods=['POST'])
def create_capture():
try:
data = request.get_json()
capture = Capture.from_dict(data)
captures_storage.append(capture)
return jsonify({'success': True, 'id': capture.id}), 201
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 400
@app.route('/api/chat', methods=['POST'])
def chat():
"""AI Assistant 'Anzuelo' for El Salvador fishing normative."""
try:
data = request.get_json()
user_message = data.get('message', '')
# Integration with Gemini (simplified for trial)
# In a real environment, we would use the google-generativeai library
api_key = os.environ.get('GEMINI_API_KEY')
system_prompt = (
"Eres la Anzuelo, una experta mística en la normativa de pesca de El Salvador (CODEPESCA). "
"Tu objetivo es ayudar a los pescadores con dudas técnicas y legales de forma clara, "
"con un toque de sabiduría del mar. Utiliza un tono amable y servicial."
)
if not api_key:
# Fallback for demonstration if no key is present
return jsonify({
'response': f"Soy Anzuelo. Recibí tu mensaje: '{user_message}'. "
"Actualmente opero con conocimiento local de CODEPESCA. "
"Recuerda que la veda de langosta termina en Junio."
})
import google.generativeai as genai
genai.configure(api_key=api_key)
model = genai.GenerativeModel('gemini-pro')
chat = model.start_chat(history=[
{"role": "user", "parts": [system_prompt]},
{"role": "model", "parts": ["Entendido. Soy Anzuelo, guía de CODEPESCA. ¿Cómo puedo asesorarte hoy?"]},
])
response = chat.send_message(user_message)
return jsonify({'response': response.text})
except Exception as e:
print(f"Chat error: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 400
@app.route('/api/sync', methods=['POST'])
def sync_captures():
"""Batch synchronization endpoint with Google Sheets forwarding."""
try:
data = request.get_json()
captures_data = data.get('captures', [])
# Store locally (in-memory for now)
for c_data in captures_data:
capture = Capture.from_dict(c_data)
captures_storage.append(capture)
# Forward to Google Sheets if configured
gas_url = os.environ.get('GAS_DEPLOYMENT_URL')
if gas_url and captures_data:
print(f"Forwarding {len(captures_data)} captures to Google Sheets...")
for c in captures_data:
# Explicitly log that these are device coordinates being forwarded
print(f" [DEVICE SYNC] Capture {c.get('id')}:")
print(f" - Port: {c.get('port')}")
print(f" - Latitude: {c.get('latitude')} (captured on device)")
print(f" - Longitude: {c.get('longitude')} (captured on device)")
print(f" - Place: {c.get('placeName')}")
try:
import requests
# Forward the exact payload expected by the GAS script
print(f"DEBUG: Forwarding to GAS URL: {gas_url[:50]}...")
response = requests.post(gas_url, json={'captures': captures_data}, timeout=15)
print(f"DEBUG: GAS Response Status: {response.status_code}")
print(f"DEBUG: GAS Response Body: {response.text}")
if not response.ok:
print(f"ERROR: Google Sheets sync failed. Status: {response.status_code}, Body: {response.text}")
else:
print(f"SUCCESS: Data accepted by Google Sheets: {response.text}")
except Exception as e:
print(f"CRITICAL: Error communicating with GAS: {str(e)}")
synced_ids = [c.get('id') for c in captures_data]
return jsonify({
'success': True,
'synced': synced_ids,
'total': len(synced_ids)
}), 200
except Exception as e:
print(f"Sync error: {str(e)}")
return jsonify({'success': False, 'error': str(e)}), 400
@app.route('/debug/files')
def debug_files():
files = []
for root, _, filenames in os.walk('static'):
for f in filenames:
rel_path = os.path.relpath(os.path.join(root, f), 'static')
files.append({
"path": rel_path,
"size": os.path.getsize(os.path.join(root, f))
})
return jsonify(files)
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port)
# Deployment trigger: 2026-01-23 18:55