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 executives; final List financialStatements; final List estimates; final List 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 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 ? json['asset'] as Map : null; final fundMap = json['fundamentals'] is Map ? json['fundamentals'] as Map : null; String extractTickerStr(dynamic val) { if (val == null) return ''; if (val is Map) { return val['ticker']?.toString() ?? ''; } return val.toString(); } String? extractExchangeStr(dynamic val) { if (val == null) return null; if (val is Map) { 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 availableTickersList = []; if (rawTickers is List) { availableTickersList = rawTickers .whereType>() .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>().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>().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((e) => CompanyExecutiveModel.fromJson(e)) .toList() ?? [], financialStatements: (json['financialStatements'] as List?) ?.whereType>() .map((e) => FinancialStatementModel.fromJson(e)) .toList() ?? [], estimates: (json['estimates'] as List?) ?.whereType>() .map((e) => ForwardEstimateModel.fromJson(e)) .toList() ?? [], availableTickers: availableTickersList, ); } @override List 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, ]; }