feat(app): add evaluation history, bot control panel, simulation visualizer, and vendored signalr_core

This commit is contained in:
2026-08-24 21:37:43 +02:00
parent 676496b77d
commit 0894c40f07
113 changed files with 12413 additions and 3613 deletions
@@ -2,7 +2,6 @@ import 'package:finlytic_app/core/network/api_client.dart';
import 'package:finlytic_app/features/asset_detail/models/fundamental_data_model.dart';
import 'package:finlytic_app/features/asset_detail/models/technical_analysis_model.dart';
import 'package:finlytic_app/features/asset_detail/models/manual_analysis_request_dto.dart';
import 'package:finlytic_app/features/asset_detail/models/manual_analysis_response_dto.dart';
import 'package:finlytic_app/features/trades/models/trade_model.dart';
import 'package:finlytic_app/features/trades/models/trade_acceptance_dto.dart';
import 'package:finlytic_app/features/trades/models/close_trade_request_dto.dart';
@@ -102,23 +101,47 @@ class AssetRepository {
return null;
}
Future<double?> getLivePrice(String isin) async {
try {
final res = await apiClient.get('/api/v1/assets/$isin/live');
if (res.statusCode == 200 && res.data != null && res.data is Map<String, dynamic>) {
final val = res.data['currentPrice'] ?? res.data['CurrentPrice'];
if (val is num) return val.toDouble();
if (val != null) return double.tryParse(val.toString());
}
} catch (_) {}
return null;
}
Future<List<TradeModel>> getAssetTrades(String isin, String? status) async {
return _tradeRepository.fetchTrades(isin: isin, status: status);
}
Future<ManualAnalysisResponseDto?> triggerManualAnalysis(String isin, {ManualAnalysisRequestDto? payload}) async {
/// Triggers an on-demand manual analysis for [isin] via `POST /api/v1/analyze/manual`.
///
/// Server contract: always `200 OK` with a full `AssetEvaluationResultDto`
/// body — even when the analysis ran but did not clear the bar for a trade
/// proposal (`AssetEvaluationResultModel.proposal == null`), the response
/// still carries the real, already-computed scores and AI reasoning, so
/// there is no more silent `204 No Content` outcome to handle here
/// (Rules.md §4). A non-2xx status (missing ISIN, engine unreachable, no
/// RPC response, unexpected error) surfaces as a `DioException` that
/// propagates to the caller instead of being swallowed into `null`.
Future<AssetEvaluationResultModel> triggerManualAnalysis(String isin, {ManualAnalysisRequestDto? payload}) async {
final body = payload != null ? payload.toJson() : {'isin': isin};
final res = await apiClient.post('/api/v1/analyze/manual', data: body);
if (res.statusCode == 200 && res.data != null && res.data is Map<String, dynamic>) {
return ManualAnalysisResponseDto.fromJson(res.data);
if (res.data != null && res.data is Map<String, dynamic>) {
return AssetEvaluationResultModel.fromJson(res.data);
}
return null;
throw StateError('Manual analysis endpoint returned an unexpected empty/non-object body.');
}
Future<void> rejectTrade(String tradeId) async => _tradeRepository.rejectTrade(tradeId);
Future<void> acceptTrade(TradeAcceptanceDto tradeAcceptanceDto) async => _tradeRepository.acceptTrade(tradeAcceptanceDto);
Future<void> closeTrade(String tradeId, double exitPrice) async =>
_tradeRepository.closeTrade(tradeId, dto: CloseTradeRequestDto(userExitPrice: exitPrice));
Future<TradeModel> addTradeFill(String tradeId, {required double executedPrice, required double quantity}) async =>
_tradeRepository.addTradeFill(tradeId, executedPrice: executedPrice, quantity: quantity);
}