AR / app.py
Gaston895's picture
Upload 13 files
54c96d3 verified
Raw
History Blame
7.28 kB
from flask import Flask, render_template, request, jsonify
from flask_cors import CORS
from datetime import datetime
import os
app = Flask(__name__)
CORS(app)
# In-memory store for the frontend base URL.
# The Cloudflare Worker calls /api/update-urls whenever the frontend URL changes.
current_urls = {
'base_url': '',
'last_updated': '',
'portals': []
}
# All 10 AEGIS analysis windows + their metadata
WINDOW_DEFINITIONS = [
{
'window': 2,
'name': 'Tech Analysis',
'path': '/window2',
'icon': 'cpu',
'color': 'blue',
'description': 'TEC model β€” technology threat scores for any year',
},
{
'window': 3,
'name': 'Conductor Synthesis',
'path': '/window3',
'icon': 'git-branch',
'color': 'purple',
'description': 'Groq synthesis of tech threats into strategic insights',
},
{
'window': 4,
'name': 'Economic Analysis',
'path': '/window4',
'icon': 'trending-up',
'color': 'yellow',
'description': '12 economic impact indicators from conductor results',
},
{
'window': 5,
'name': 'War Analysis',
'path': '/window5',
'icon': 'shield',
'color': 'red',
'description': 'Dual AI + live conflict data β€” war risk predictions',
},
{
'window': 6,
'name': 'Climate Analysis',
'path': '/window6',
'icon': 'cloud',
'color': 'cyan',
'description': 'World Bank CCKP β€” climate-conflict nexus assessment',
},
{
'window': 7,
'name': 'Disease Prediction',
'path': '/window7',
'icon': 'activity',
'color': 'orange',
'description': 'AI biosecurity risk β€” top 3 disease outbreak predictions',
},
{
'window': 8,
'name': 'Drug Discovery',
'path': '/window8',
'icon': 'zap',
'color': 'green',
'description': '4-phase pipeline β€” SMILES, DiffDock, Vina, manufacturing',
},
{
'window': 9,
'name': 'Visual Disease',
'path': '/window9',
'icon': 'image',
'color': 'pink',
'description': 'AI-generated pathogen morphology visualizations',
},
{
'window': 10,
'name': 'Visual Kinetic',
'path': '/window10',
'icon': 'eye',
'color': 'indigo',
'description': '3D PK/PD simulation with live cardiac monitoring',
},
{
'window': 11,
'name': 'AR Portal',
'path': '/window11',
'icon': 'scan',
'color': 'teal',
'description': 'Augmented Reality β€” scan QR to view all windows remotely',
},
]
def build_portals(base_url: str) -> list:
"""Build portal list from window definitions + a given base URL."""
return [
{
**w,
'url': f"{base_url.rstrip('/')}{w['path']}",
'qr_url': f"{base_url.rstrip('/')}{w['path']}",
}
for w in WINDOW_DEFINITIONS
]
# ── Routes ────────────────────────────────────────────────────────────────────
@app.route('/')
def index():
"""AR Portal Dashboard β€” shows QR grid for all windows."""
return render_template(
'ar_dashboard.html',
base_url=current_urls.get('base_url', ''),
last_updated=current_urls.get('last_updated', ''),
portals=current_urls.get('portals', []),
)
@app.route('/ping', methods=['GET'])
def ping():
"""UptimeRobot keep-alive β€” returns 200 instantly, no heavy work."""
return jsonify({'status': 'ok', 'message': 'AR Space is alive'}), 200
@app.route('/health')
def health():
"""Health check."""
return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})
@app.route('/api/status')
def status():
"""Current AR system status β€” called by the worker."""
return jsonify({
'status': 'online',
'base_url': current_urls.get('base_url', ''),
'last_updated': current_urls.get('last_updated', ''),
'portals_count': len(current_urls.get('portals', [])),
'total_windows': len(WINDOW_DEFINITIONS),
})
@app.route('/api/update-urls', methods=['POST'])
def update_urls():
"""
Called by the Cloudflare Worker (or directly) to register the frontend
base URL. Once set, /api/qr-data returns ready-to-use QR links.
"""
try:
data = request.get_json()
if not data or 'base_url' not in data:
return jsonify({'error': 'base_url is required'}), 400
base_url = data['base_url'].rstrip('/')
portals = build_portals(base_url)
timestamp = datetime.now().isoformat()
current_urls.update({
'base_url': base_url,
'last_updated': timestamp,
'portals': portals,
})
return jsonify({
'success': True,
'message': 'URLs updated successfully',
'base_url': base_url,
'timestamp': timestamp,
'portals_count': len(portals),
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/qr-data', methods=['GET'])
def qr_data():
"""
Returns all window URLs ready for QR-code generation.
Window 11 frontend calls this via the worker proxy to render the QR grid.
Optional ?window=N to get a single window's data.
"""
window_param = request.args.get('window')
base_url = current_urls.get('base_url', '')
if not base_url:
return jsonify({
'error': 'base_url not configured yet',
'hint': 'POST {"base_url": "https://your-app.com"} to /api/update-urls',
'portals': [],
}), 503
portals = current_urls.get('portals') or build_portals(base_url)
if window_param:
try:
wnum = int(window_param)
portal = next((p for p in portals if p['window'] == wnum), None)
if not portal:
return jsonify({'error': f'Window {wnum} not found'}), 404
return jsonify({'success': True, 'portal': portal})
except ValueError:
return jsonify({'error': 'window must be an integer'}), 400
return jsonify({
'success': True,
'base_url': base_url,
'last_updated': current_urls.get('last_updated', ''),
'portals': portals,
'total': len(portals),
})
@app.route('/api/windows', methods=['GET'])
def windows():
"""Static list of all window definitions (no base_url needed)."""
return jsonify({
'success': True,
'windows': WINDOW_DEFINITIONS,
'total': len(WINDOW_DEFINITIONS),
})
if __name__ == '__main__':
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port, debug=False)