202 lines
6.5 KiB
Python
202 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
from flask import Flask, jsonify, request
|
|
from flask_cors import CORS
|
|
import json
|
|
import h5py
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
app = Flask(__name__)
|
|
CORS(app)
|
|
|
|
# Load index once at startup
|
|
with open('/data/index.json', 'r') as f:
|
|
INDEX = json.load(f)
|
|
|
|
H5_DIR = Path('/data/h5')
|
|
|
|
@app.route('/api/nodes', methods=['GET'])
|
|
def get_nodes():
|
|
nodes_list = []
|
|
for node_id, node_info in INDEX['nodes'].items():
|
|
file_count = len(node_info.get('files', []))
|
|
nodes_list.append({
|
|
'id': node_id,
|
|
'position': node_info.get('position', {}),
|
|
'file_count': file_count,
|
|
'hasDates': file_count > 0
|
|
})
|
|
return jsonify({
|
|
'nodes': nodes_list,
|
|
'sampleRateHz': 500
|
|
})
|
|
|
|
@app.route('/api/dates', methods=['GET'])
|
|
def get_dates():
|
|
return jsonify({'dates': INDEX['dates']})
|
|
|
|
@app.route('/api/migration-status', methods=['GET'])
|
|
def get_migration_status():
|
|
"""Status de la conversion RAW -> H5"""
|
|
total_files = 345 # Connu du projet
|
|
h5_files = list(H5_DIR.glob('*.h5'))
|
|
converted = len(h5_files)
|
|
|
|
return jsonify({
|
|
'summary': {
|
|
'total_files': total_files,
|
|
'converted_files': converted,
|
|
'percentage': round(converted / total_files * 100, 1),
|
|
'status': 'in_progress' if converted < total_files else 'complete'
|
|
},
|
|
'h5_files_available': converted
|
|
})
|
|
|
|
@app.route('/api/rms-timeline', methods=['GET'])
|
|
def get_rms_timeline():
|
|
"""Timeline RMS pour un channel et une date donnés"""
|
|
date = request.args.get('date')
|
|
channel = request.args.get('channel', 'ch0')
|
|
|
|
if not date:
|
|
return jsonify({'error': 'date parameter required'}), 400
|
|
|
|
# Chercher les fichiers H5 pour cette date
|
|
timeline = []
|
|
for node_id, node_info in INDEX['nodes'].items():
|
|
for file_info in node_info.get('files', []):
|
|
if file_info.get('date') == date:
|
|
# Pour l'instant, retourner des données mock
|
|
# TODO: Calculer le vrai RMS depuis les fichiers H5
|
|
timeline.append({
|
|
'node_id': node_id,
|
|
'timestamp': 0,
|
|
'rms': 0 # À calculer depuis H5
|
|
})
|
|
|
|
return jsonify({
|
|
'date': date,
|
|
'channel': channel,
|
|
'timeline': timeline
|
|
})
|
|
|
|
@app.route('/api/data', methods=['GET'])
|
|
def get_waveform_data():
|
|
"""Données waveform pour un node, date, channel, timestamp"""
|
|
node_id = request.args.get('node')
|
|
date = request.args.get('date')
|
|
channel = request.args.get('channel', 'ch0')
|
|
start = float(request.args.get('start', 0))
|
|
duration = float(request.args.get('duration', 10))
|
|
|
|
if not node_id or not date:
|
|
return jsonify({'error': 'node and date required'}), 400
|
|
|
|
# Chercher le fichier H5 correspondant
|
|
node_info = INDEX['nodes'].get(node_id)
|
|
if not node_info:
|
|
return jsonify({'error': 'node not found'}), 404
|
|
|
|
h5_file = None
|
|
for file_info in node_info.get('files', []):
|
|
if file_info.get('date') == date:
|
|
h5_file = H5_DIR / file_info.get('filename', '')
|
|
break
|
|
|
|
if not h5_file or not h5_file.exists():
|
|
return jsonify({'error': 'H5 file not found'}), 404
|
|
|
|
try:
|
|
with h5py.File(h5_file, 'r') as f:
|
|
sample_rate = f['metadata'].attrs['sample_rate_hz']
|
|
channel_num = int(channel.replace('ch', '').replace('CH', ''))
|
|
|
|
# Lire les données calibrées
|
|
dataset = f[f'calibrated_data/channel_{channel_num + 1}']
|
|
start_sample = int(start * sample_rate)
|
|
end_sample = int((start + duration) * sample_rate)
|
|
|
|
# Limiter à la taille du dataset
|
|
end_sample = min(end_sample, dataset.shape[0])
|
|
|
|
if start_sample >= dataset.shape[0]:
|
|
return jsonify({'error': 'start time out of range'}), 400
|
|
|
|
data = dataset[start_sample:end_sample]
|
|
|
|
# Calculer stats
|
|
rms = float(np.sqrt(np.mean(data ** 2)))
|
|
peak = float(np.max(np.abs(data)))
|
|
|
|
return jsonify({
|
|
'node_id': node_id,
|
|
'date': date,
|
|
'channel': channel,
|
|
'start': start,
|
|
'duration': duration,
|
|
'sample_rate': int(sample_rate),
|
|
'data': data.tolist(),
|
|
'stats': {
|
|
'rms': rms,
|
|
'peak': peak,
|
|
'samples': len(data)
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
@app.route('/api/h5/files', methods=['GET'])
|
|
def get_h5_files():
|
|
"""Liste des fichiers H5 disponibles avec métadonnées"""
|
|
files = []
|
|
for h5_path in H5_DIR.glob('*.h5'):
|
|
try:
|
|
with h5py.File(h5_path, 'r') as f:
|
|
files.append({
|
|
'filename': h5_path.name,
|
|
'size_mb': round(h5_path.stat().st_size / 1024 / 1024, 2),
|
|
'duration_sec': int(f['metadata'].attrs.get('duration_sec', 0)),
|
|
'sample_rate': int(f['metadata'].attrs.get('sample_rate_hz', 500)),
|
|
'channels': int(f['metadata'].attrs.get('n_channels', 4))
|
|
})
|
|
except:
|
|
pass
|
|
|
|
return jsonify({'files': files, 'count': len(files)})
|
|
|
|
@app.route('/api/h5/coverage', methods=['GET'])
|
|
def get_h5_coverage():
|
|
"""Matrice de couverture nodes x dates"""
|
|
coverage = {}
|
|
for node_id, node_info in INDEX['nodes'].items():
|
|
node_dates = [f['date'] for f in node_info.get('files', []) if 'date' in f]
|
|
coverage[node_id] = node_dates
|
|
|
|
return jsonify({
|
|
'coverage': coverage,
|
|
'total_nodes': len(coverage),
|
|
'total_dates': len(INDEX['dates'])
|
|
})
|
|
|
|
@app.route('/api/chat', methods=['POST'])
|
|
def chat():
|
|
"""Endpoint chat assistant (mock)"""
|
|
data = request.json
|
|
message = data.get('message', '')
|
|
|
|
return jsonify({
|
|
'response': f"[Mock] Vous avez dit: {message}",
|
|
'timestamp': datetime.now().isoformat()
|
|
})
|
|
|
|
@app.route('/health', methods=['GET'])
|
|
def health():
|
|
return jsonify({'status': 'ok', 'nodes': len(INDEX['nodes']), 'dates': len(INDEX['dates'])})
|
|
|
|
if __name__ == '__main__':
|
|
print(f"Loaded {len(INDEX['nodes'])} nodes, {len(INDEX['dates'])} dates")
|
|
print(f"H5 directory: {H5_DIR}")
|
|
app.run(host='0.0.0.0', port=3004)
|