Refactor code for improved readability and consistency
- Cleaned up whitespace and formatting in various files including http.py, language.py, logger.py, safe_exec.py, and SQL migration scripts. - Consolidated import statements and removed unnecessary blank lines. - Updated logging configuration for better clarity. - Enhanced the safe execution code with improved error handling and logging. - Removed commented-out code and unnecessary variables in backfill_zero_trades.py and other scripts. - Added a pyproject.toml for Ruff and Vulture configuration. - Introduced requirements-dev.txt for development dependencies. - Removed commented-out stock entries in init.sql for cleaner migration scripts.
This commit is contained in:
@@ -3,19 +3,21 @@ Fast Analysis API Routes
|
||||
|
||||
New high-performance analysis endpoints that replace the slow multi-agent system.
|
||||
"""
|
||||
from flask import Blueprint, request, jsonify, g
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
||||
from app.utils.auth import login_required
|
||||
from app.utils.logger import get_logger
|
||||
from app.services.fast_analysis import get_fast_analysis_service
|
||||
from flask import Blueprint, g, jsonify, request
|
||||
|
||||
from app.services.analysis_memory import get_analysis_memory
|
||||
from app.services.billing_service import get_billing_service
|
||||
from app.services.fast_analysis import get_fast_analysis_service
|
||||
from app.utils.auth import login_required
|
||||
from app.utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
fast_analysis_bp = Blueprint('fast_analysis', __name__)
|
||||
fast_analysis_bp = Blueprint("fast_analysis", __name__)
|
||||
|
||||
# In-memory in-flight guard to avoid duplicate analysis charges caused by rapid repeated clicks.
|
||||
_analysis_inflight_lock = threading.Lock()
|
||||
@@ -28,19 +30,22 @@ def _try_refund_credits(user_id: int, amount: int, remark: str):
|
||||
if int(amount or 0) <= 0:
|
||||
return
|
||||
billing = get_billing_service()
|
||||
billing.add_credits(
|
||||
user_id=int(user_id),
|
||||
amount=int(amount),
|
||||
action='refund',
|
||||
remark=remark
|
||||
)
|
||||
billing.add_credits(user_id=int(user_id), amount=int(amount), action="refund", remark=remark)
|
||||
except Exception as e:
|
||||
logger.error(f"Async auto refund failed: {e}", exc_info=True)
|
||||
|
||||
|
||||
def _run_async_analysis_task(task_memory_id: int, market: str, symbol: str, language: str,
|
||||
model: str, timeframe: str, user_id: int, inflight_key: str,
|
||||
credits_charged: int = 0):
|
||||
def _run_async_analysis_task(
|
||||
task_memory_id: int,
|
||||
market: str,
|
||||
symbol: str,
|
||||
language: str,
|
||||
model: str,
|
||||
timeframe: str,
|
||||
user_id: int,
|
||||
inflight_key: str,
|
||||
credits_charged: int = 0,
|
||||
):
|
||||
"""
|
||||
Background worker: execute analysis and update pending history record.
|
||||
"""
|
||||
@@ -48,19 +53,14 @@ def _run_async_analysis_task(task_memory_id: int, market: str, symbol: str, lang
|
||||
service = get_fast_analysis_service()
|
||||
memory = get_analysis_memory()
|
||||
result = service.analyze(
|
||||
market=market,
|
||||
symbol=symbol,
|
||||
language=language,
|
||||
model=model,
|
||||
timeframe=timeframe,
|
||||
user_id=user_id
|
||||
market=market, symbol=symbol, language=language, model=model, timeframe=timeframe, user_id=user_id
|
||||
)
|
||||
memory.finalize_pending_task(task_memory_id, result)
|
||||
if result.get("error"):
|
||||
_try_refund_credits(
|
||||
user_id=int(user_id),
|
||||
amount=int(credits_charged or 0),
|
||||
remark=f'Auto refund: async fast-analysis failed ({market}:{symbol}:{timeframe})'
|
||||
remark=f"Auto refund: async fast-analysis failed ({market}:{symbol}:{timeframe})",
|
||||
)
|
||||
|
||||
# analyze() already stores a separate memory row; remove it to avoid duplicates.
|
||||
@@ -75,7 +75,7 @@ def _run_async_analysis_task(task_memory_id: int, market: str, symbol: str, lang
|
||||
_try_refund_credits(
|
||||
user_id=int(user_id),
|
||||
amount=int(credits_charged or 0),
|
||||
remark=f'Auto refund: async fast-analysis exception ({market}:{symbol}:{timeframe})'
|
||||
remark=f"Auto refund: async fast-analysis exception ({market}:{symbol}:{timeframe})",
|
||||
)
|
||||
try:
|
||||
get_analysis_memory().fail_pending_task(task_memory_id, str(e))
|
||||
@@ -110,12 +110,12 @@ def _release_inflight(key: str):
|
||||
_analysis_inflight.pop(key, None)
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/analyze', methods=['POST'])
|
||||
@fast_analysis_bp.route("/analyze", methods=["POST"])
|
||||
@login_required
|
||||
def analyze():
|
||||
"""
|
||||
Fast AI analysis for any symbol.
|
||||
|
||||
|
||||
POST /api/fast-analysis/analyze
|
||||
Body: {
|
||||
"market": "Crypto" | "USStock" | "Forex" | ...,
|
||||
@@ -124,39 +124,37 @@ def analyze():
|
||||
"model": "openai/gpt-4o" (optional),
|
||||
"timeframe": "1D" (optional)
|
||||
}
|
||||
|
||||
|
||||
Returns:
|
||||
Fast analysis result with actionable recommendations.
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
market = (data.get('market') or '').strip()
|
||||
symbol = (data.get('symbol') or '').strip()
|
||||
language = data.get('language', 'en-US')
|
||||
model = data.get('model')
|
||||
timeframe = data.get('timeframe', '1D')
|
||||
async_submit = bool(data.get('async_submit', False))
|
||||
|
||||
|
||||
market = (data.get("market") or "").strip()
|
||||
symbol = (data.get("symbol") or "").strip()
|
||||
language = data.get("language", "en-US")
|
||||
model = data.get("model")
|
||||
timeframe = data.get("timeframe", "1D")
|
||||
async_submit = bool(data.get("async_submit", False))
|
||||
|
||||
if not market or not symbol:
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'market and symbol are required',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
return jsonify({"code": 0, "msg": "market and symbol are required", "data": None}), 400
|
||||
|
||||
# Get current user's ID to associate analysis with user
|
||||
user_id = getattr(g, 'user_id', None)
|
||||
user_id = getattr(g, "user_id", None)
|
||||
if not user_id:
|
||||
return jsonify({'code': 0, 'msg': 'Unauthorized', 'data': None}), 401
|
||||
return jsonify({"code": 0, "msg": "Unauthorized", "data": None}), 401
|
||||
|
||||
inflight_key = _build_inflight_key(user_id, market, symbol, timeframe)
|
||||
if not _acquire_inflight(inflight_key, ttl_sec=90):
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'Analysis already in progress for this symbol/timeframe. Please wait.',
|
||||
'data': {'in_progress': True}
|
||||
}), 429
|
||||
return jsonify(
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "Analysis already in progress for this symbol/timeframe. Please wait.",
|
||||
"data": {"in_progress": True},
|
||||
}
|
||||
), 429
|
||||
|
||||
# Billing / credits (best-effort, consistent with polymarket deep analysis)
|
||||
credits_charged = 0
|
||||
@@ -166,30 +164,32 @@ def analyze():
|
||||
try:
|
||||
billing = get_billing_service()
|
||||
if billing.is_billing_enabled():
|
||||
credits_charged = int(billing.get_feature_cost('ai_analysis') or 0)
|
||||
credits_charged = int(billing.get_feature_cost("ai_analysis") or 0)
|
||||
if credits_charged > 0:
|
||||
ok, msg = billing.check_and_consume(
|
||||
user_id=int(user_id),
|
||||
feature='ai_analysis',
|
||||
reference_id=f"fast_analysis_{market}:{symbol}:{timeframe}"
|
||||
feature="ai_analysis",
|
||||
reference_id=f"fast_analysis_{market}:{symbol}:{timeframe}",
|
||||
)
|
||||
if not ok:
|
||||
# Standardize insufficient credits message
|
||||
if str(msg or "").startswith('insufficient_credits'):
|
||||
if str(msg or "").startswith("insufficient_credits"):
|
||||
# Format: insufficient_credits:<current>:<cost>
|
||||
parts = str(msg).split(':')
|
||||
parts = str(msg).split(":")
|
||||
cur = float(parts[1]) if len(parts) >= 2 else 0.0
|
||||
req = float(parts[2]) if len(parts) >= 3 else float(credits_charged)
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'Insufficient credits',
|
||||
'data': {
|
||||
'required': req,
|
||||
'current': cur,
|
||||
'shortage': max(0.0, req - cur),
|
||||
return jsonify(
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "Insufficient credits",
|
||||
"data": {
|
||||
"required": req,
|
||||
"current": cur,
|
||||
"shortage": max(0.0, req - cur),
|
||||
},
|
||||
}
|
||||
}), 400
|
||||
return jsonify({'code': 0, 'msg': f'Failed to deduct credits: {msg}', 'data': None}), 500
|
||||
), 400
|
||||
return jsonify({"code": 0, "msg": f"Failed to deduct credits: {msg}", "data": None}), 500
|
||||
billing_consumed = True
|
||||
# Query remaining credits after successful consumption
|
||||
try:
|
||||
@@ -199,155 +199,157 @@ def analyze():
|
||||
except Exception as e:
|
||||
# Billing failure should not crash analysis by default, but should be visible in logs.
|
||||
logger.warning(f"Billing check failed (skipped): {e}", exc_info=True)
|
||||
|
||||
|
||||
service = get_fast_analysis_service()
|
||||
|
||||
# Async submit mode: record "processing" immediately and return task id.
|
||||
if async_submit:
|
||||
memory = get_analysis_memory()
|
||||
pending_id = memory.create_pending_task(
|
||||
market=market,
|
||||
symbol=symbol,
|
||||
language=language,
|
||||
model=model or "",
|
||||
timeframe=timeframe,
|
||||
user_id=user_id
|
||||
market=market, symbol=symbol, language=language, model=model or "", timeframe=timeframe, user_id=user_id
|
||||
)
|
||||
if not pending_id:
|
||||
return jsonify({'code': 0, 'msg': 'Failed to create analysis task', 'data': None}), 500
|
||||
return jsonify({"code": 0, "msg": "Failed to create analysis task", "data": None}), 500
|
||||
|
||||
t = threading.Thread(
|
||||
target=_run_async_analysis_task,
|
||||
args=(int(pending_id), market, symbol, language, model, timeframe, int(user_id), inflight_key, int(credits_charged or 0)),
|
||||
daemon=True
|
||||
args=(
|
||||
int(pending_id),
|
||||
market,
|
||||
symbol,
|
||||
language,
|
||||
model,
|
||||
timeframe,
|
||||
int(user_id),
|
||||
inflight_key,
|
||||
int(credits_charged or 0),
|
||||
),
|
||||
daemon=True,
|
||||
)
|
||||
t.start()
|
||||
# worker owns inflight release
|
||||
inflight_key = None
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'submitted',
|
||||
'data': {
|
||||
'task_id': int(pending_id),
|
||||
'memory_id': int(pending_id),
|
||||
'status': 'processing',
|
||||
'market': market,
|
||||
'symbol': symbol,
|
||||
'timeframe': timeframe,
|
||||
'credits_charged': credits_charged,
|
||||
'remaining_credits': remaining_credits,
|
||||
return jsonify(
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "submitted",
|
||||
"data": {
|
||||
"task_id": int(pending_id),
|
||||
"memory_id": int(pending_id),
|
||||
"status": "processing",
|
||||
"market": market,
|
||||
"symbol": symbol,
|
||||
"timeframe": timeframe,
|
||||
"credits_charged": credits_charged,
|
||||
"remaining_credits": remaining_credits,
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
result = service.analyze(
|
||||
market=market,
|
||||
symbol=symbol,
|
||||
language=language,
|
||||
model=model,
|
||||
timeframe=timeframe,
|
||||
user_id=user_id
|
||||
market=market, symbol=symbol, language=language, model=model, timeframe=timeframe, user_id=user_id
|
||||
)
|
||||
|
||||
if result.get('error'):
|
||||
|
||||
if result.get("error"):
|
||||
# Best-effort refund if we already charged but analysis failed.
|
||||
if billing_consumed and billing and credits_charged > 0:
|
||||
try:
|
||||
billing.add_credits(
|
||||
user_id=int(user_id),
|
||||
amount=int(credits_charged),
|
||||
action='refund',
|
||||
remark=f'Auto refund: fast-analysis failed ({market}:{symbol}:{timeframe})'
|
||||
action="refund",
|
||||
remark=f"Auto refund: fast-analysis failed ({market}:{symbol}:{timeframe})",
|
||||
)
|
||||
remaining_credits = float(billing.get_user_credits(int(user_id)))
|
||||
except Exception as re:
|
||||
logger.error(f"Auto refund failed: {re}", exc_info=True)
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': result['error'],
|
||||
'data': result
|
||||
}), 500
|
||||
|
||||
return jsonify({"code": 0, "msg": result["error"], "data": result}), 500
|
||||
|
||||
# memory_id is already set in service.analyze() -> _store_analysis_memory()
|
||||
# No need to store again here (would create duplicates)
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
'data': {
|
||||
**(result or {}),
|
||||
'credits_charged': credits_charged,
|
||||
'remaining_credits': remaining_credits,
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
**(result or {}),
|
||||
"credits_charged": credits_charged,
|
||||
"remaining_credits": remaining_credits,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Best-effort refund on unexpected error after charge.
|
||||
try:
|
||||
if 'billing_consumed' in locals() and billing_consumed and 'billing' in locals() and billing and credits_charged > 0 and 'user_id' in locals() and user_id:
|
||||
if (
|
||||
"billing_consumed" in locals()
|
||||
and billing_consumed
|
||||
and "billing" in locals()
|
||||
and billing
|
||||
and credits_charged > 0
|
||||
and "user_id" in locals()
|
||||
and user_id
|
||||
):
|
||||
billing.add_credits(
|
||||
user_id=int(user_id),
|
||||
amount=int(credits_charged),
|
||||
action='refund',
|
||||
remark=f'Auto refund: fast-analysis exception ({market}:{symbol}:{timeframe})'
|
||||
action="refund",
|
||||
remark=f"Auto refund: fast-analysis exception ({market}:{symbol}:{timeframe})",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.error(f"Fast analysis API failed: {e}", exc_info=True)
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
finally:
|
||||
try:
|
||||
if 'inflight_key' in locals() and inflight_key:
|
||||
if "inflight_key" in locals() and inflight_key:
|
||||
_release_inflight(inflight_key)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/analyze-legacy', methods=['POST'])
|
||||
@fast_analysis_bp.route("/analyze-legacy", methods=["POST"])
|
||||
@login_required
|
||||
def analyze_legacy():
|
||||
"""
|
||||
Fast analysis with legacy format output.
|
||||
For backward compatibility with existing frontend.
|
||||
|
||||
|
||||
POST /api/fast-analysis/analyze-legacy
|
||||
Body: Same as /analyze
|
||||
|
||||
|
||||
Returns:
|
||||
Result in multi-agent format for frontend compatibility.
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
market = (data.get('market') or '').strip()
|
||||
symbol = (data.get('symbol') or '').strip()
|
||||
language = data.get('language', 'en-US')
|
||||
model = data.get('model')
|
||||
timeframe = data.get('timeframe', '1D')
|
||||
|
||||
|
||||
market = (data.get("market") or "").strip()
|
||||
symbol = (data.get("symbol") or "").strip()
|
||||
language = data.get("language", "en-US")
|
||||
model = data.get("model")
|
||||
timeframe = data.get("timeframe", "1D")
|
||||
|
||||
if not market or not symbol:
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'market and symbol are required',
|
||||
'data': None
|
||||
}), 400
|
||||
return jsonify({"code": 0, "msg": "market and symbol are required", "data": None}), 400
|
||||
|
||||
# Billing / credits (same behavior as /analyze)
|
||||
user_id = getattr(g, 'user_id', None)
|
||||
user_id = getattr(g, "user_id", None)
|
||||
if not user_id:
|
||||
return jsonify({'code': 0, 'msg': 'Unauthorized', 'data': None}), 401
|
||||
return jsonify({"code": 0, "msg": "Unauthorized", "data": None}), 401
|
||||
|
||||
inflight_key = _build_inflight_key(user_id, market, symbol, timeframe)
|
||||
if not _acquire_inflight(inflight_key, ttl_sec=90):
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'Analysis already in progress for this symbol/timeframe. Please wait.',
|
||||
'data': {'in_progress': True}
|
||||
}), 429
|
||||
return jsonify(
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "Analysis already in progress for this symbol/timeframe. Please wait.",
|
||||
"data": {"in_progress": True},
|
||||
}
|
||||
), 429
|
||||
|
||||
credits_charged = 0
|
||||
remaining_credits = None
|
||||
@@ -356,28 +358,30 @@ def analyze_legacy():
|
||||
try:
|
||||
billing = get_billing_service()
|
||||
if billing.is_billing_enabled():
|
||||
credits_charged = int(billing.get_feature_cost('ai_analysis') or 0)
|
||||
credits_charged = int(billing.get_feature_cost("ai_analysis") or 0)
|
||||
if credits_charged > 0:
|
||||
ok, msg = billing.check_and_consume(
|
||||
user_id=int(user_id),
|
||||
feature='ai_analysis',
|
||||
reference_id=f"fast_analysis_legacy_{market}:{symbol}:{timeframe}"
|
||||
feature="ai_analysis",
|
||||
reference_id=f"fast_analysis_legacy_{market}:{symbol}:{timeframe}",
|
||||
)
|
||||
if not ok:
|
||||
if str(msg or "").startswith('insufficient_credits'):
|
||||
parts = str(msg).split(':')
|
||||
if str(msg or "").startswith("insufficient_credits"):
|
||||
parts = str(msg).split(":")
|
||||
cur = float(parts[1]) if len(parts) >= 2 else 0.0
|
||||
req = float(parts[2]) if len(parts) >= 3 else float(credits_charged)
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'Insufficient credits',
|
||||
'data': {
|
||||
'required': req,
|
||||
'current': cur,
|
||||
'shortage': max(0.0, req - cur),
|
||||
return jsonify(
|
||||
{
|
||||
"code": 0,
|
||||
"msg": "Insufficient credits",
|
||||
"data": {
|
||||
"required": req,
|
||||
"current": cur,
|
||||
"shortage": max(0.0, req - cur),
|
||||
},
|
||||
}
|
||||
}), 400
|
||||
return jsonify({'code': 0, 'msg': f'Failed to deduct credits: {msg}', 'data': None}), 500
|
||||
), 400
|
||||
return jsonify({"code": 0, "msg": f"Failed to deduct credits: {msg}", "data": None}), 500
|
||||
billing_consumed = True
|
||||
try:
|
||||
remaining_credits = float(billing.get_user_credits(int(user_id)))
|
||||
@@ -385,192 +389,161 @@ def analyze_legacy():
|
||||
remaining_credits = None
|
||||
except Exception as e:
|
||||
logger.warning(f"Billing check failed (skipped): {e}", exc_info=True)
|
||||
|
||||
|
||||
service = get_fast_analysis_service()
|
||||
result = service.analyze_legacy_format(
|
||||
market=market,
|
||||
symbol=symbol,
|
||||
language=language,
|
||||
model=model,
|
||||
timeframe=timeframe
|
||||
market=market, symbol=symbol, language=language, model=model, timeframe=timeframe
|
||||
)
|
||||
|
||||
if result.get('error'):
|
||||
|
||||
if result.get("error"):
|
||||
if billing_consumed and billing and credits_charged > 0:
|
||||
try:
|
||||
billing.add_credits(
|
||||
user_id=int(user_id),
|
||||
amount=int(credits_charged),
|
||||
action='refund',
|
||||
remark=f'Auto refund: fast-analysis-legacy failed ({market}:{symbol}:{timeframe})'
|
||||
action="refund",
|
||||
remark=f"Auto refund: fast-analysis-legacy failed ({market}:{symbol}:{timeframe})",
|
||||
)
|
||||
remaining_credits = float(billing.get_user_credits(int(user_id)))
|
||||
except Exception as re:
|
||||
logger.error(f"Auto refund failed (legacy): {re}", exc_info=True)
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': result['error'],
|
||||
'data': result
|
||||
}), 500
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
'data': {
|
||||
**(result or {}),
|
||||
'credits_charged': credits_charged,
|
||||
'remaining_credits': remaining_credits,
|
||||
return jsonify({"code": 0, "msg": result["error"], "data": result}), 500
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
**(result or {}),
|
||||
"credits_charged": credits_charged,
|
||||
"remaining_credits": remaining_credits,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
if 'billing_consumed' in locals() and billing_consumed and 'billing' in locals() and billing and credits_charged > 0 and 'user_id' in locals() and user_id:
|
||||
if (
|
||||
"billing_consumed" in locals()
|
||||
and billing_consumed
|
||||
and "billing" in locals()
|
||||
and billing
|
||||
and credits_charged > 0
|
||||
and "user_id" in locals()
|
||||
and user_id
|
||||
):
|
||||
billing.add_credits(
|
||||
user_id=int(user_id),
|
||||
amount=int(credits_charged),
|
||||
action='refund',
|
||||
remark=f'Auto refund: fast-analysis-legacy exception ({market}:{symbol}:{timeframe})'
|
||||
action="refund",
|
||||
remark=f"Auto refund: fast-analysis-legacy exception ({market}:{symbol}:{timeframe})",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
logger.error(f"Fast analysis legacy API failed: {e}", exc_info=True)
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
finally:
|
||||
try:
|
||||
if 'inflight_key' in locals() and inflight_key:
|
||||
if "inflight_key" in locals() and inflight_key:
|
||||
_release_inflight(inflight_key)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/history', methods=['GET'])
|
||||
@fast_analysis_bp.route("/history", methods=["GET"])
|
||||
@login_required
|
||||
def get_history():
|
||||
"""
|
||||
Get analysis history for a symbol.
|
||||
|
||||
|
||||
GET /api/fast-analysis/history?market=Crypto&symbol=BTC/USDT&days=7&limit=10
|
||||
"""
|
||||
try:
|
||||
market = request.args.get('market', '').strip()
|
||||
symbol = request.args.get('symbol', '').strip()
|
||||
days = int(request.args.get('days', 7))
|
||||
limit = min(int(request.args.get('limit', 10)), 50)
|
||||
|
||||
market = request.args.get("market", "").strip()
|
||||
symbol = request.args.get("symbol", "").strip()
|
||||
days = int(request.args.get("days", 7))
|
||||
limit = min(int(request.args.get("limit", 10)), 50)
|
||||
|
||||
if not market or not symbol:
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'market and symbol are required',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
return jsonify({"code": 0, "msg": "market and symbol are required", "data": None}), 400
|
||||
|
||||
memory = get_analysis_memory()
|
||||
history = memory.get_recent(market, symbol, days, limit)
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
'data': {
|
||||
'items': history,
|
||||
'total': len(history)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
return jsonify({"code": 1, "msg": "success", "data": {"items": history, "total": len(history)}})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get history failed: {e}")
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/history/all', methods=['GET'])
|
||||
@fast_analysis_bp.route("/history/all", methods=["GET"])
|
||||
@login_required
|
||||
def get_all_history():
|
||||
"""
|
||||
Get all analysis history with pagination.
|
||||
|
||||
|
||||
GET /api/fast-analysis/history/all?page=1&pagesize=20
|
||||
"""
|
||||
try:
|
||||
page = int(request.args.get('page', 1))
|
||||
pagesize = min(int(request.args.get('pagesize', 20)), 50)
|
||||
|
||||
page = int(request.args.get("page", 1))
|
||||
pagesize = min(int(request.args.get("pagesize", 20)), 50)
|
||||
|
||||
# Get current user's ID to filter history
|
||||
user_id = getattr(g, 'user_id', None)
|
||||
|
||||
user_id = getattr(g, "user_id", None)
|
||||
|
||||
memory = get_analysis_memory()
|
||||
result = memory.get_all_history(user_id=user_id, page=page, page_size=pagesize)
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
'data': {
|
||||
'list': result['items'],
|
||||
'total': result['total'],
|
||||
'page': result['page'],
|
||||
'pagesize': result['page_size']
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"list": result["items"],
|
||||
"total": result["total"],
|
||||
"page": result["page"],
|
||||
"pagesize": result["page_size"],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get all history failed: {e}")
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/history/<int:memory_id>', methods=['DELETE'])
|
||||
@fast_analysis_bp.route("/history/<int:memory_id>", methods=["DELETE"])
|
||||
@login_required
|
||||
def delete_history(memory_id: int):
|
||||
"""
|
||||
Delete a history record.
|
||||
|
||||
|
||||
DELETE /api/fast-analysis/history/123
|
||||
"""
|
||||
try:
|
||||
# Get current user's ID to ensure they can only delete their own records
|
||||
user_id = getattr(g, 'user_id', None)
|
||||
|
||||
user_id = getattr(g, "user_id", None)
|
||||
|
||||
memory = get_analysis_memory()
|
||||
success = memory.delete_history(memory_id, user_id=user_id)
|
||||
|
||||
|
||||
if success:
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'Deleted successfully',
|
||||
'data': None
|
||||
})
|
||||
return jsonify({"code": 1, "msg": "Deleted successfully", "data": None})
|
||||
else:
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'Record not found or no permission',
|
||||
'data': None
|
||||
}), 404
|
||||
|
||||
return jsonify({"code": 0, "msg": "Record not found or no permission", "data": None}), 404
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Delete history failed: {e}")
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/feedback', methods=['POST'])
|
||||
@fast_analysis_bp.route("/feedback", methods=["POST"])
|
||||
@login_required
|
||||
def submit_feedback():
|
||||
"""
|
||||
Submit user feedback on an analysis.
|
||||
|
||||
|
||||
POST /api/fast-analysis/feedback
|
||||
Body: {
|
||||
"memory_id": 123,
|
||||
@@ -579,119 +552,89 @@ def submit_feedback():
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
memory_id = int(data.get('memory_id', 0))
|
||||
feedback = (data.get('feedback') or '').strip()
|
||||
|
||||
|
||||
memory_id = int(data.get("memory_id", 0))
|
||||
feedback = (data.get("feedback") or "").strip()
|
||||
|
||||
if not memory_id or not feedback:
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'memory_id and feedback are required',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
valid_feedback = ['helpful', 'not_helpful', 'accurate', 'inaccurate']
|
||||
return jsonify({"code": 0, "msg": "memory_id and feedback are required", "data": None}), 400
|
||||
|
||||
valid_feedback = ["helpful", "not_helpful", "accurate", "inaccurate"]
|
||||
if feedback not in valid_feedback:
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': f'feedback must be one of: {valid_feedback}',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
return jsonify({"code": 0, "msg": f"feedback must be one of: {valid_feedback}", "data": None}), 400
|
||||
|
||||
memory = get_analysis_memory()
|
||||
success = memory.record_feedback(memory_id, feedback)
|
||||
|
||||
return jsonify({
|
||||
'code': 1 if success else 0,
|
||||
'msg': 'success' if success else 'failed',
|
||||
'data': None
|
||||
})
|
||||
|
||||
|
||||
return jsonify({"code": 1 if success else 0, "msg": "success" if success else "failed", "data": None})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Submit feedback failed: {e}")
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/performance', methods=['GET'])
|
||||
@fast_analysis_bp.route("/performance", methods=["GET"])
|
||||
@login_required
|
||||
def get_performance():
|
||||
"""
|
||||
Get AI analysis performance statistics.
|
||||
|
||||
|
||||
GET /api/fast-analysis/performance?market=Crypto&symbol=BTC/USDT&days=30
|
||||
"""
|
||||
try:
|
||||
market = request.args.get('market', '').strip() or None
|
||||
symbol = request.args.get('symbol', '').strip() or None
|
||||
days = int(request.args.get('days', 30))
|
||||
|
||||
market = request.args.get("market", "").strip() or None
|
||||
symbol = request.args.get("symbol", "").strip() or None
|
||||
days = int(request.args.get("days", 30))
|
||||
|
||||
memory = get_analysis_memory()
|
||||
stats = memory.get_performance_stats(market, symbol, days)
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
'data': stats
|
||||
})
|
||||
|
||||
|
||||
return jsonify({"code": 1, "msg": "success", "data": stats})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get performance failed: {e}")
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
|
||||
|
||||
@fast_analysis_bp.route('/similar-patterns', methods=['GET'])
|
||||
@fast_analysis_bp.route("/similar-patterns", methods=["GET"])
|
||||
@login_required
|
||||
def get_similar_patterns():
|
||||
"""
|
||||
Get similar historical patterns for current market conditions.
|
||||
|
||||
|
||||
GET /api/fast-analysis/similar-patterns?market=Crypto&symbol=BTC/USDT
|
||||
"""
|
||||
try:
|
||||
market = request.args.get('market', '').strip()
|
||||
symbol = request.args.get('symbol', '').strip()
|
||||
|
||||
market = request.args.get("market", "").strip()
|
||||
symbol = request.args.get("symbol", "").strip()
|
||||
|
||||
if not market or not symbol:
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': 'market and symbol are required',
|
||||
'data': None
|
||||
}), 400
|
||||
|
||||
return jsonify({"code": 0, "msg": "market and symbol are required", "data": None}), 400
|
||||
|
||||
# Get current indicators
|
||||
service = get_fast_analysis_service()
|
||||
data = service._collect_market_data(market, symbol)
|
||||
indicators = data.get('indicators', {})
|
||||
|
||||
indicators = data.get("indicators", {})
|
||||
|
||||
# Find similar patterns
|
||||
memory = get_analysis_memory()
|
||||
patterns = memory.get_similar_patterns(market, symbol, indicators)
|
||||
|
||||
return jsonify({
|
||||
'code': 1,
|
||||
'msg': 'success',
|
||||
'data': {
|
||||
'patterns': patterns,
|
||||
'current_indicators': {
|
||||
'rsi': indicators.get('rsi', {}).get('value'),
|
||||
'macd_signal': indicators.get('macd', {}).get('signal'),
|
||||
'trend': indicators.get('moving_averages', {}).get('trend'),
|
||||
}
|
||||
|
||||
return jsonify(
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"patterns": patterns,
|
||||
"current_indicators": {
|
||||
"rsi": indicators.get("rsi", {}).get("value"),
|
||||
"macd_signal": indicators.get("macd", {}).get("signal"),
|
||||
"trend": indicators.get("moving_averages", {}).get("trend"),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Get similar patterns failed: {e}")
|
||||
return jsonify({
|
||||
'code': 0,
|
||||
'msg': str(e),
|
||||
'data': None
|
||||
}), 500
|
||||
return jsonify({"code": 0, "msg": str(e), "data": None}), 500
|
||||
|
||||
Reference in New Issue
Block a user