Files
Finlytic/FinlyticApp/lib/features/asset_detail/models/fundamental_data_model.dart
T

353 lines
16 KiB
Dart

import 'package:equatable/equatable.dart';
import 'ticker_model.dart';
import 'company_officer_model.dart';
import 'financial_statement_model.dart';
import 'forward_estimate_model.dart';
export 'ticker_model.dart';
export 'company_officer_model.dart';
export 'financial_statement_model.dart';
export 'forward_estimate_model.dart';
class FundamentalDataModel extends Equatable {
final String isin;
final String primaryTicker;
final String ticker;
final String companyName;
final String? exchange;
final String? tradingCurrency;
final String? businessSummary;
final String? sector;
final String? industry;
final String? country;
final int? employees;
final double currentPrice;
final double dayChangeAbsolute;
final double dayChangePercent;
final double? fiftyTwoWeekHigh;
final double? fiftyTwoWeekLow;
final double? marketCapitalization;
final double? enterpriseValue;
final double? peRatioTrailing;
final double? peRatioForward;
final double? pegRatio;
final double? pbRatio;
final double? psRatio;
final double? evToEbitda;
final double? evToRevenue;
final double? totalRevenue;
final double? revenueGrowthYoY;
final double? grossProfit;
final double? ebitda;
final double? dilutedEps;
final double? totalCash;
final double? totalDebt;
final double? operatingCashFlow;
final double? freeCashFlow;
final double? grossMargin;
final double? operatingMargin;
final double? netProfitMargin;
final double? returnOnEquity;
final double? returnOnAssets;
final double? returnOnInvestedCapital;
final double? debtToEquity;
final double? currentRatio;
final double? quickRatio;
final double? interestCoverage;
final double? dividendYield;
final double? payoutRatio;
final String? exDividendDate;
final String? nextEarningsDate;
final double? percentHeldByInstitutions;
final double? percentHeldByInsiders;
final double? shortRatio;
final double? shortPercentOfFloat;
final String? consensusRating;
final double? priceTargetLow;
final double? priceTargetHigh;
final double? priceTargetMedian;
final double? priceTargetMean;
final List<CompanyExecutiveModel> executives;
final List<FinancialStatementModel> financialStatements;
final List<ForwardEstimateModel> estimates;
final List<TickerModel> availableTickers;
const FundamentalDataModel({
required this.isin,
required this.primaryTicker,
required this.ticker,
required this.companyName,
this.exchange,
this.tradingCurrency,
this.businessSummary,
this.sector,
this.industry,
this.country,
this.employees,
required this.currentPrice,
required this.dayChangeAbsolute,
required this.dayChangePercent,
this.fiftyTwoWeekHigh,
this.fiftyTwoWeekLow,
this.marketCapitalization,
this.enterpriseValue,
this.peRatioTrailing,
this.peRatioForward,
this.pegRatio,
this.pbRatio,
this.psRatio,
this.evToEbitda,
this.evToRevenue,
this.totalRevenue,
this.revenueGrowthYoY,
this.grossProfit,
this.ebitda,
this.dilutedEps,
this.totalCash,
this.totalDebt,
this.operatingCashFlow,
this.freeCashFlow,
this.grossMargin,
this.operatingMargin,
this.netProfitMargin,
this.returnOnEquity,
this.returnOnAssets,
this.returnOnInvestedCapital,
this.debtToEquity,
this.currentRatio,
this.quickRatio,
this.interestCoverage,
this.dividendYield,
this.payoutRatio,
this.exDividendDate,
this.nextEarningsDate,
this.percentHeldByInstitutions,
this.percentHeldByInsiders,
this.shortRatio,
this.shortPercentOfFloat,
this.consensusRating,
this.priceTargetLow,
this.priceTargetHigh,
this.priceTargetMedian,
this.priceTargetMean,
required this.executives,
required this.financialStatements,
required this.estimates,
this.availableTickers = const [],
});
factory FundamentalDataModel.fromJson(Map<String, dynamic> json) {
double? parseNullableDouble(dynamic val) {
if (val == null) return null;
if (val is num) return val.toDouble();
return double.tryParse(val.toString());
}
final assetMap = json['asset'] is Map<String, dynamic> ? json['asset'] as Map<String, dynamic> : null;
final fundMap = json['fundamentals'] is Map<String, dynamic> ? json['fundamentals'] as Map<String, dynamic> : null;
String extractTickerStr(dynamic val) {
if (val == null) return '';
if (val is Map<String, dynamic>) {
return val['ticker']?.toString() ?? '';
}
return val.toString();
}
String? extractExchangeStr(dynamic val) {
if (val == null) return null;
if (val is Map<String, dynamic>) {
return val['exchange']?.toString();
}
return null;
}
final isinVal = assetMap?['isin']?.toString() ?? json['isin']?.toString() ?? '';
final primaryTickerVal = extractTickerStr(assetMap?['primaryTicker'] ?? json['primaryTicker']);
final tickerVal = extractTickerStr(fundMap?['ticker'] ?? json['ticker']).isNotEmpty
? extractTickerStr(fundMap?['ticker'] ?? json['ticker'])
: primaryTickerVal;
final companyNameVal = assetMap?['name']?.toString() ?? json['companyName']?.toString() ?? tickerVal;
final businessSummaryVal = assetMap?['description']?.toString() ?? json['businessSummary']?.toString();
final exchangeVal = extractExchangeStr(fundMap?['ticker']) ??
extractExchangeStr(assetMap?['primaryTicker']) ??
json['exchange']?.toString();
final rawTickers = assetMap?['availableTickers'] ?? json['availableTickers'];
List<TickerModel> availableTickersList = [];
if (rawTickers is List) {
availableTickersList = rawTickers
.whereType<Map<String, dynamic>>()
.map((t) => TickerModel.fromJson(t))
.toList();
}
final totalRev = parseNullableDouble(fundMap?['totalRevenue'] ?? json['totalRevenue']);
final rawGrossProfit = parseNullableDouble(fundMap?['grossProfit'] ?? json['grossProfit']);
double? grossMarginVal = parseNullableDouble(fundMap?['grossMargins'] ?? fundMap?['grossMargin'] ?? json['grossMargin']);
double? grossProfVal = rawGrossProfit;
if (rawGrossProfit != null) {
if (rawGrossProfit <= 1.0 && rawGrossProfit >= 0.0) {
grossMarginVal ??= rawGrossProfit;
if (totalRev != null && totalRev > 0) {
grossProfVal = rawGrossProfit * totalRev;
}
} else if (totalRev != null && totalRev > 0) {
grossMarginVal ??= rawGrossProfit / totalRev;
}
}
final evVal = parseNullableDouble(fundMap?['enterpriseValue'] ?? json['enterpriseValue']);
double? evToRevVal = parseNullableDouble(fundMap?['evToRevenue'] ?? fundMap?['enterpriseValueToRevenue'] ?? json['evToRevenue']);
if (evToRevVal == null && evVal != null && totalRev != null && totalRev > 0) {
evToRevVal = evVal / totalRev;
}
String? exDivDateStr = fundMap?['exDividendDate']?.toString() ?? json['exDividendDate']?.toString();
String? nextEarningsDateStr = fundMap?['nextEarningsDate']?.toString() ?? json['nextEarningsDate']?.toString();
final rawEvents = json['events'];
if (rawEvents is List) {
final now = DateTime.now();
final divEvents = rawEvents.whereType<Map<String, dynamic>>().where((e) {
final t = e['type']?.toString().toUpperCase() ?? '';
return t == 'DIVIDEND' || t == 'EX_DIVIDEND';
}).toList();
if (exDivDateStr == null && divEvents.isNotEmpty) {
divEvents.sort((a, b) {
final da = DateTime.tryParse(a['date']?.toString() ?? '') ?? DateTime(1970);
final db = DateTime.tryParse(b['date']?.toString() ?? '') ?? DateTime(1970);
return da.compareTo(db);
});
final upcoming = divEvents.firstWhere((e) {
final d = DateTime.tryParse(e['date']?.toString() ?? '');
return d != null && d.isAfter(now.subtract(const Duration(days: 7)));
}, orElse: () => divEvents.last);
exDivDateStr = upcoming['date']?.toString();
}
final earningsEvents = rawEvents.whereType<Map<String, dynamic>>().where((e) {
final t = e['type']?.toString().toUpperCase() ?? '';
return t.contains('EARNINGS');
}).toList();
if (nextEarningsDateStr == null && earningsEvents.isNotEmpty) {
earningsEvents.sort((a, b) {
final da = DateTime.tryParse(a['date']?.toString() ?? '') ?? DateTime(1970);
final db = DateTime.tryParse(b['date']?.toString() ?? '') ?? DateTime(1970);
return da.compareTo(db);
});
final upcoming = earningsEvents.firstWhere((e) {
final d = DateTime.tryParse(e['date']?.toString() ?? '');
return d != null && d.isAfter(now.subtract(const Duration(days: 1)));
}, orElse: () => earningsEvents.last);
nextEarningsDateStr = upcoming['date']?.toString();
}
}
return FundamentalDataModel(
isin: isinVal,
primaryTicker: primaryTickerVal,
ticker: tickerVal,
companyName: companyNameVal,
exchange: exchangeVal,
tradingCurrency: fundMap?['currency']?.toString() ?? json['tradingCurrency']?.toString(),
businessSummary: businessSummaryVal,
sector: assetMap?['sector']?.toString() ?? json['sector']?.toString(),
industry: assetMap?['industry']?.toString() ?? json['industry']?.toString(),
country: assetMap?['country']?.toString() ?? json['country']?.toString(),
employees: (assetMap?['employees'] ?? json['employees']) is int
? (assetMap?['employees'] ?? json['employees']) as int
: int.tryParse((assetMap?['employees'] ?? json['employees'])?.toString() ?? ''),
currentPrice: parseNullableDouble(fundMap?['currentPrice'] ?? json['currentPrice']) ?? 0.0,
dayChangeAbsolute: parseNullableDouble(fundMap?['dayChangeAbsolute'] ?? json['dayChangeAbsolute']) ?? 0.0,
dayChangePercent: parseNullableDouble(fundMap?['dayChangePercent'] ?? json['dayChangePercent']) ?? 0.0,
fiftyTwoWeekHigh: parseNullableDouble(fundMap?['fiftyTwoWeekHigh'] ?? json['fiftyTwoWeekHigh']),
fiftyTwoWeekLow: parseNullableDouble(fundMap?['fiftyTwoWeekLow'] ?? json['fiftyTwoWeekLow']),
marketCapitalization: parseNullableDouble(fundMap?['marketCap'] ?? fundMap?['marketCapitalization'] ?? json['marketCapitalization']),
enterpriseValue: evVal,
peRatioTrailing: parseNullableDouble(fundMap?['trailingPe'] ?? fundMap?['trailingPE'] ?? fundMap?['peRatioTrailing'] ?? json['peRatioTrailing'] ?? json['trailingPe']),
peRatioForward: parseNullableDouble(fundMap?['forwardPe'] ?? fundMap?['forwardPE'] ?? fundMap?['peRatioForward'] ?? json['peRatioForward'] ?? json['forwardPe']),
pegRatio: parseNullableDouble(fundMap?['pegRatio'] ?? json['pegRatio']),
pbRatio: parseNullableDouble(fundMap?['priceToBook'] ?? fundMap?['pbRatio'] ?? json['pbRatio']),
psRatio: parseNullableDouble(fundMap?['priceToSales'] ?? fundMap?['priceToSalesTrailing12Months'] ?? fundMap?['psRatio'] ?? json['psRatio']),
evToEbitda: parseNullableDouble(fundMap?['evToEbitda'] ?? fundMap?['enterpriseToEbitda'] ?? json['evToEbitda']),
evToRevenue: evToRevVal,
totalRevenue: totalRev,
revenueGrowthYoY: parseNullableDouble(fundMap?['revenueGrowthYoY'] ?? fundMap?['revenueGrowth'] ?? json['revenueGrowthYoY']),
grossProfit: grossProfVal,
ebitda: parseNullableDouble(fundMap?['ebitda'] ?? json['ebitda']),
dilutedEps: parseNullableDouble(fundMap?['dilutedEps'] ?? fundMap?['trailingEps'] ?? json['dilutedEps']),
totalCash: parseNullableDouble(fundMap?['totalCash'] ?? json['totalCash']),
totalDebt: parseNullableDouble(fundMap?['totalDebt'] ?? json['totalDebt']),
operatingCashFlow: parseNullableDouble(fundMap?['operatingCashFlow'] ?? fundMap?['operatingCashflow'] ?? json['operatingCashFlow']),
freeCashFlow: parseNullableDouble(fundMap?['freeCashFlow'] ?? fundMap?['freeCashflow'] ?? json['freeCashFlow']),
grossMargin: grossMarginVal,
operatingMargin: parseNullableDouble(fundMap?['operatingIncome'] ?? fundMap?['operatingMargins'] ?? fundMap?['operatingMargin'] ?? json['operatingMargin']),
netProfitMargin: parseNullableDouble(fundMap?['netIncome'] ?? fundMap?['profitMargins'] ?? fundMap?['netProfitMargin'] ?? json['netProfitMargin']),
returnOnEquity: parseNullableDouble(fundMap?['returnOnEquity'] ?? json['returnOnEquity']),
returnOnAssets: parseNullableDouble(fundMap?['returnOnAssets'] ?? json['returnOnAssets']),
returnOnInvestedCapital: parseNullableDouble(fundMap?['returnOnInvestedCapital'] ?? json['returnOnInvestedCapital']),
debtToEquity: parseNullableDouble(fundMap?['debtToEquity'] ?? json['debtToEquity']),
currentRatio: parseNullableDouble(fundMap?['currentRatio'] ?? json['currentRatio']),
quickRatio: parseNullableDouble(fundMap?['quickRatio'] ?? json['quickRatio']),
interestCoverage: parseNullableDouble(fundMap?['interestCoverage'] ?? json['interestCoverage']),
dividendYield: parseNullableDouble(fundMap?['forwardDividendYield'] ?? fundMap?['dividendYield'] ?? json['dividendYield']),
payoutRatio: parseNullableDouble(fundMap?['payoutRatio'] ?? json['payoutRatio']),
exDividendDate: exDivDateStr,
nextEarningsDate: nextEarningsDateStr,
percentHeldByInstitutions: parseNullableDouble(fundMap?['percentHeldByInstitutions'] ?? json['percentHeldByInstitutions']),
percentHeldByInsiders: parseNullableDouble(fundMap?['percentHeldByInsiders'] ?? json['percentHeldByInsiders']),
shortRatio: parseNullableDouble(fundMap?['shortRatio'] ?? json['shortRatio']),
shortPercentOfFloat: parseNullableDouble(fundMap?['shortPercentOfFloat'] ?? json['shortPercentOfFloat']),
consensusRating: fundMap?['consensusRating']?.toString() ?? json['consensusRating']?.toString(),
priceTargetLow: parseNullableDouble(fundMap?['priceTargetLow'] ?? json['priceTargetLow']),
priceTargetHigh: parseNullableDouble(fundMap?['priceTargetHigh'] ?? json['priceTargetHigh']),
priceTargetMedian: parseNullableDouble(fundMap?['priceTargetMedian'] ?? json['priceTargetMedian']),
priceTargetMean: parseNullableDouble(fundMap?['priceTargetMean'] ?? json['priceTargetMean']),
executives: (json['executives'] as List?)
?.whereType<Map<String, dynamic>>()
.map((e) => CompanyExecutiveModel.fromJson(e))
.toList() ??
[],
financialStatements: (json['financialStatements'] as List?)
?.whereType<Map<String, dynamic>>()
.map((e) => FinancialStatementModel.fromJson(e))
.toList() ??
[],
estimates: (json['estimates'] as List?)
?.whereType<Map<String, dynamic>>()
.map((e) => ForwardEstimateModel.fromJson(e))
.toList() ??
[],
availableTickers: availableTickersList,
);
}
@override
List<Object?> get props => [
isin, primaryTicker, ticker, companyName, exchange, tradingCurrency,
businessSummary, sector, industry, country, employees, currentPrice,
dayChangeAbsolute, dayChangePercent, fiftyTwoWeekHigh, fiftyTwoWeekLow,
marketCapitalization, enterpriseValue, peRatioTrailing, peRatioForward,
pegRatio, pbRatio, psRatio, evToEbitda, evToRevenue, grossMargin,
operatingMargin, netProfitMargin, returnOnEquity, returnOnAssets,
returnOnInvestedCapital, debtToEquity, currentRatio, quickRatio,
dividendYield, payoutRatio, exDividendDate, nextEarningsDate,
percentHeldByInstitutions, percentHeldByInsiders, shortRatio,
shortPercentOfFloat, consensusRating, priceTargetLow, priceTargetHigh,
priceTargetMedian, priceTargetMean, executives, financialStatements,
estimates, availableTickers,
];
}