Initial FELDMAN implementation - Completed flows registry

- Flask API for storing completed flows
- Receives OK flows from ALFRED/JARED
- Receives corrected data from MASON
- Docker deployment on port 5054
This commit is contained in:
ARCHITECT
2025-12-24 10:32:40 +00:00
commit 5e135466ec
7 changed files with 201 additions and 0 deletions

7
.env.example Normal file
View File

@@ -0,0 +1,7 @@
H_INSTANCIA=your_h_instancia_here
DB_HOST=172.17.0.1
DB_PORT=5432
DB_NAME=corp
DB_USER=corp
DB_PASSWORD=your_password_here
PORT=5054

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5054
CMD ["gunicorn", "--bind", "0.0.0.0:5054", "--workers", "2", "app:app"]

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
# FELDMAN - Completed Flows Registry
Registro de flujos completados exitosamente en el sistema TZZR.
## Descripcion
FELDMAN recibe y almacena:
- Flujos OK directamente desde ALFRED/JARED
- Datos corregidos desde MASON
## Endpoints
| Endpoint | Metodo | Auth | Descripcion |
|----------|--------|------|-------------|
| `/health` | GET | No | Health check |
| `/s-contract` | GET | No | Contrato del servicio |
| `/recibir` | POST | Si | Recibir flujo completado |
| `/completados` | GET | Si | Listar completados |
| `/stats` | GET | Si | Estadisticas |
## Puerto
5054

124
app.py Normal file
View File

@@ -0,0 +1,124 @@
from flask import Flask, request, jsonify
from functools import wraps
import psycopg2
from psycopg2.extras import RealDictCursor
import os
import hashlib
from datetime import datetime
app = Flask(__name__)
H_INSTANCIA = os.environ.get('H_INSTANCIA')
DB_HOST = os.environ.get('DB_HOST', '172.17.0.1')
DB_PORT = os.environ.get('DB_PORT', '5432')
DB_NAME = os.environ.get('DB_NAME', 'corp')
DB_USER = os.environ.get('DB_USER', 'corp')
DB_PASSWORD = os.environ.get('DB_PASSWORD', 'corp')
PORT = int(os.environ.get('PORT', 5054))
def get_db():
return psycopg2.connect(
host=DB_HOST, port=DB_PORT, database=DB_NAME,
user=DB_USER, password=DB_PASSWORD,
cursor_factory=RealDictCursor
)
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth_key = request.headers.get('X-Auth-Key')
if not auth_key or auth_key != H_INSTANCIA:
return jsonify({'error': 'Unauthorized'}), 401
return f(*args, **kwargs)
return decorated
def generate_hash(data):
return hashlib.sha256(f"{data}{datetime.now().isoformat()}".encode()).hexdigest()[:64]
@app.route('/health', methods=['GET'])
def health():
try:
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT 1')
cur.close()
conn.close()
return jsonify({'status': 'healthy', 'service': 'feldman', 'version': '1.0.0'})
except Exception as e:
return jsonify({'status': 'unhealthy', 'error': str(e)}), 500
@app.route('/s-contract', methods=['GET'])
def s_contract():
return jsonify({
'service': 'feldman',
'version': '1.0.0',
'contract_version': 'S-CONTRACT v2.1',
'description': 'Receives routed OK flows from ALFRED/JARED',
'endpoints': {
'/health': {'method': 'GET', 'auth': False},
'/recibir': {'method': 'POST', 'auth': True, 'desc': 'Receive completed flow'},
'/completados': {'method': 'GET', 'auth': True, 'desc': 'List completed flows'},
'/stats': {'method': 'GET', 'auth': True, 'desc': 'Statistics'}
}
})
@app.route('/recibir', methods=['POST'])
@require_auth
def recibir():
data = request.get_json() or {}
h_completado = generate_hash(str(data))
conn = get_db()
cur = conn.cursor()
cur.execute('''
INSERT INTO completados
(h_completado, h_instancia_origen, h_ejecucion, flujo_nombre, datos, notas)
VALUES (%s, %s, %s, %s, %s, %s)
RETURNING id, h_completado, created_at
''', (
h_completado,
data.get('h_instancia_origen', 'unknown'),
data.get('h_ejecucion', ''),
data.get('flujo_nombre', ''),
psycopg2.extras.Json(data),
data.get('notas', '')
))
result = cur.fetchone()
conn.commit()
cur.close()
conn.close()
return jsonify({'success': True, 'registro': dict(result), 'service': 'feldman'})
@app.route('/completados', methods=['GET'])
@require_auth
def completados():
limit = request.args.get('limit', 50, type=int)
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT * FROM completados ORDER BY created_at DESC LIMIT %s', (limit,))
records = cur.fetchall()
cur.close()
conn.close()
return jsonify({'completados': [dict(r) for r in records], 'count': len(records)})
@app.route('/stats', methods=['GET'])
@require_auth
def stats():
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT COUNT(*) as total FROM completados')
total = cur.fetchone()['total']
cur.execute('''
SELECT DATE(created_at) as fecha, COUNT(*) as count
FROM completados
GROUP BY DATE(created_at)
ORDER BY fecha DESC LIMIT 7
''')
por_dia = {str(r['fecha']): r['count'] for r in cur.fetchall()}
cur.close()
conn.close()
return jsonify({'total': total, 'por_dia': por_dia})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=PORT, debug=False)

23
docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
version: "3.8"
services:
feldman:
build: .
container_name: feldman-service
restart: unless-stopped
ports:
- "5054:5054"
environment:
- H_INSTANCIA=${H_INSTANCIA}
- DB_HOST=${DB_HOST}
- DB_PORT=${DB_PORT}
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- PORT=5054
extra_hosts:
- "host.docker.internal:host-gateway"
networks:
- tzzr-network
networks:
tzzr-network:
external: true

14
init.sql Normal file
View File

@@ -0,0 +1,14 @@
-- FELDMAN tables
CREATE TABLE IF NOT EXISTS completados (
id BIGSERIAL PRIMARY KEY,
h_completado VARCHAR(64) NOT NULL UNIQUE,
h_instancia_origen VARCHAR(64) NOT NULL,
h_ejecucion VARCHAR(64),
flujo_nombre VARCHAR(100),
datos JSONB DEFAULT '{}',
notas TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_completados_origen ON completados(h_instancia_origen);

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
flask==3.0.0
psycopg2-binary==2.9.9
gunicorn==21.2.0