1→import Foundation 2→ 3→// MARK: - Institution Types 4→ 5→enum InstitutionType: String, CaseIterable, Identifiable { 6→ case banks = "Bancos" 7→ case cooperatives = "Cooperativas" 8→ case consortia = "Consórcios" 9→ case sociedades = "Sociedades" 10→ 11→ var id: String { rawValue } 12→ 13→ var icon: String { 14→ switch self { 15→ case .banks: return "building.columns.fill" 16→ case .cooperatives: return "person.3.fill" 17→ case .consortia: return "person.2.fill" 18→ case .sociedades: return "building.2.fill" 19→ } 20→ } 21→ 22→ var color: String { 23→ switch self { 24→ case .banks: return "#3B82F6" 25→ case .cooperatives: return "#10B981" 26→ case .consortia: return "#F59E0B" 27→ case .sociedades: return "#8B5CF6" 28→ } 29→ } 30→} 31→ 32→// MARK: - Bank Model 33→ 34→struct Bank: Codable, Identifiable, Hashable { 35→ var id: String { cnpj } 36→ 37→ let cnpj: String 38→ let name: String 39→ let segment: String? 40→ let address: String? 41→ let complement: String? 42→ let neighborhood: String? 43→ let cep: String? 44→ let city: String? 45→ let state: String? 46→ let ddd: String? 47→ let phone: String? 48→ let hasCommercialPortfolio: String? 49→ let email: String? 50→ let website: String? 51→ let ibgeCode: String? 52→ 53→ enum CodingKeys: String, CodingKey { 54→ case cnpj = "CNPJ" 55→ case name = "NOME_INSTITUICAO" 56→ case segment = "SEGMENTO" 57→ case address = "ENDERECO" 58→ case complement = "COMPLEMENTO" 59→ case neighborhood = "BAIRRO" 60→ case cep = "CEP" 61→ case city = "MUNICIPIO" 62→ case state = "UF" 63→ case ddd = "DDD" 64→ case phone = "TELEFONE" 65→ case hasCommercialPortfolio = "CARTEIRA_COMERCIAL" 66→ case email = "E_MAIL" 67→ case website = "SITIO_NA_INTERNET" 68→ case ibgeCode = "MUNICIPIO_IBGE" 69→ } 70→ 71→ var displayName: String { 72→ name 73→ } 74→ 75→ var formattedPhone: String { 76→ guard let ddd = ddd, let phone = phone else { return "--" } 77→ return "(\(ddd)) \(phone)" 78→ } 79→ 80→ var formattedCNPJ: String { 81→ guard cnpj.count >= 8 else { return cnpj } 82→ let chars = Array(cnpj) 83→ if chars.count == 8 { 84→ return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))" 85→ } 86→ return cnpj 87→ } 88→ 89→ var fullAddress: String { 90→ var parts: [String] = [] 91→ if let addr = address { parts.append(addr) } 92→ if let comp = complement { parts.append(comp) } 93→ if let bairro = neighborhood { parts.append(bairro) } 94→ if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") } 95→ if let cepVal = cep { parts.append("CEP: \(cepVal)") } 96→ return parts.joined(separator: ", ") 97→ } 98→} 99→ 100→struct BanksResponse: Codable { 101→ let value: [Bank] 102→} 103→ 104→// MARK: - Cooperative Model 105→ 106→struct Cooperative: Codable, Identifiable, Hashable { 107→ var id: String { cnpj } 108→ 109→ let cnpj: String 110→ let name: String 111→ let address: String? 112→ let complement: String? 113→ let neighborhood: String? 114→ let cep: String? 115→ let city: String? 116→ let state: String? 117→ let ddd: String? 118→ let phone: String? 119→ let coopClass: String? 120→ let association: String? 121→ let category: String? 122→ let affiliation: String? 123→ let email: String? 124→ let website: String? 125→ let ibgeCode: String? 126→ 127→ enum CodingKeys: String, CodingKey { 128→ case cnpj = "CNPJ" 129→ case name = "NOME_INSTITUICAO" 130→ case address = "ENDERECO" 131→ case complement = "COMPLEMENTO" 132→ case neighborhood = "BAIRRO" 133→ case cep = "CEP" 134→ case city = "MUNICIPIO" 135→ case state = "UF" 136→ case ddd = "DDD" 137→ case phone = "TELEFONE" 138→ case coopClass = "CLASSE" 139→ case association = "ASSOCIACAO" 140→ case category = "CATEGCOOPSING" 141→ case affiliation = "FILIACAO" 142→ case email = "E_MAIL" 143→ case website = "SITIO_NA_INTERNET" 144→ case ibgeCode = "MUNICIPIO_IBGE" 145→ } 146→ 147→ var displayName: String { 148→ // Shorten long names 149→ if name.count > 60 { 150→ return String(name.prefix(57)) + "..." 151→ } 152→ return name 153→ } 154→ 155→ var formattedPhone: String { 156→ guard let ddd = ddd, let phone = phone else { return "--" } 157→ return "(\(ddd)) \(phone)" 158→ } 159→ 160→ var formattedCNPJ: String { 161→ guard cnpj.count >= 8 else { return cnpj } 162→ let chars = Array(cnpj) 163→ if chars.count == 8 { 164→ return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))" 165→ } 166→ return cnpj 167→ } 168→ 169→ var fullAddress: String { 170→ var parts: [String] = [] 171→ if let addr = address { parts.append(addr) } 172→ if let comp = complement { parts.append(comp) } 173→ if let bairro = neighborhood { parts.append(bairro) } 174→ if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") } 175→ if let cepVal = cep { parts.append("CEP: \(cepVal)") } 176→ return parts.joined(separator: ", ") 177→ } 178→} 179→ 180→struct CooperativesResponse: Codable { 181→ let value: [Cooperative] 182→} 183→ 184→// MARK: - Consortium Model 185→ 186→struct Consortium: Codable, Identifiable, Hashable { 187→ var id: String { cnpj } 188→ 189→ let cnpj: String 190→ let name: String 191→ let address: String? 192→ let complement: String? 193→ let neighborhood: String? 194→ let cep: String? 195→ let city: String? 196→ let state: String? 197→ let ddd: String? 198→ let phone: String? 199→ let email: String? 200→ let website: String? 201→ let ibgeCode: String? 202→ 203→ enum CodingKeys: String, CodingKey { 204→ case cnpj = "CNPJ" 205→ case name = "NOME_INSTITUICAO" 206→ case address = "ENDERECO" 207→ case complement = "COMPLEMENTO" 208→ case neighborhood = "BAIRRO" 209→ case cep = "CEP" 210→ case city = "MUNICIPIO" 211→ case state = "UF" 212→ case ddd = "DDD" 213→ case phone = "TELEFONE" 214→ case email = "E_MAIL" 215→ case website = "SITIO_NA_INTERNET" 216→ case ibgeCode = "MUNICIPIO_IBGE" 217→ } 218→ 219→ var displayName: String { 220→ name 221→ } 222→ 223→ var formattedPhone: String { 224→ guard let ddd = ddd, let phone = phone else { return "--" } 225→ return "(\(ddd)) \(phone)" 226→ } 227→ 228→ var formattedCNPJ: String { 229→ guard cnpj.count >= 8 else { return cnpj } 230→ let chars = Array(cnpj) 231→ if chars.count == 8 { 232→ return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))" 233→ } 234→ return cnpj 235→ } 236→ 237→ var fullAddress: String { 238→ var parts: [String] = [] 239→ if let addr = address { parts.append(addr) } 240→ if let comp = complement { parts.append(comp) } 241→ if let bairro = neighborhood { parts.append(bairro) } 242→ if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") } 243→ if let cepVal = cep { parts.append("CEP: \(cepVal)") } 244→ return parts.joined(separator: ", ") 245→ } 246→} 247→ 248→struct ConsortiaResponse: Codable { 249→ let value: [Consortium] 250→} 251→ 252→// MARK: - Sociedade Model 253→ 254→struct Sociedade: Codable, Identifiable, Hashable { 255→ var id: String { cnpj } 256→ 257→ let cnpj: String 258→ let name: String 259→ let segment: String? 260→ let address: String? 261→ let complement: String? 262→ let neighborhood: String? 263→ let cep: String? 264→ let city: String? 265→ let state: String? 266→ let ddd: String? 267→ let phone: String? 268→ let email: String? 269→ let website: String? 270→ let ibgeCode: String? 271→ 272→ enum CodingKeys: String, CodingKey { 273→ case cnpj = "CNPJ" 274→ case name = "NOME_INSTITUICAO" 275→ case segment = "SEGMENTO" 276→ case address = "ENDERECO" 277→ case complement = "COMPLEMENTO" 278→ case neighborhood = "BAIRRO" 279→ case cep = "CEP" 280→ case city = "MUNICIPIO" 281→ case state = "UF" 282→ case ddd = "DDD" 283→ case phone = "TELEFONE" 284→ case email = "E_MAIL" 285→ case website = "SITIO_NA_INTERNET" 286→ case ibgeCode = "MUNICIPIO_IBGE" 287→ } 288→ 289→ var displayName: String { 290→ name 291→ } 292→ 293→ var formattedPhone: String { 294→ guard let ddd = ddd, let phone = phone else { return "--" } 295→ return "(\(ddd)) \(phone)" 296→ } 297→ 298→ var formattedCNPJ: String { 299→ guard cnpj.count >= 8 else { return cnpj } 300→ let chars = Array(cnpj) 301→ if chars.count == 8 { 302→ return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))" 303→ } 304→ return cnpj 305→ } 306→ 307→ var fullAddress: String { 308→ var parts: [String] = [] 309→ if let addr = address { parts.append(addr) } 310→ if let comp = complement { parts.append(comp) } 311→ if let bairro = neighborhood { parts.append(bairro) } 312→ if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") } 313→ if let cepVal = cep { parts.append("CEP: \(cepVal)") } 314→ return parts.joined(separator: ", ") 315→ } 316→ 317→ var segmentType: SociedadeSegment { 318→ guard let seg = segment?.uppercased() else { return .other } 319→ 320→ if seg.contains("CORRETORA DE CÂMBIO") { 321→ return .exchangeBroker 322→ } else if seg.contains("CRÉDITO DIRETO") || seg.contains("SCD") { 323→ return .directCredit 324→ } else if seg.contains("MICROEMPREENDEDOR") { 325→ return .microfinance 326→ } else if seg.contains("ARRENDAMENTO") { 327→ return .leasing 328→ } else if seg.contains("FINANCIAMENTO") { 329→ return .financing 330→ } else if seg.contains("CORRETORA") || seg.contains("TVM") { 331→ return .stockBroker 332→ } else if seg.contains("DISTRIBUIDORA") { 333→ return .distributor 334→ } else if seg.contains("CRÉDITO IMOBILIÁRIO") { 335→ return .realEstate 336→ } else if seg.contains("PAGAMENTO") { 337→ return .payment 338→ } else { 339→ return .other 340→ } 341→ } 342→} 343→ 344→enum SociedadeSegment: String, CaseIterable { 345→ case exchangeBroker = "Corretora de Câmbio" 346→ case directCredit = "SCD (Fintech)" 347→ case microfinance = "Microcrédito" 348→ case leasing = "Arrendamento" 349→ case financing = "Financeira" 350→ case stockBroker = "Corretora TVM" 351→ case distributor = "Distribuidora TVM" 352→ case realEstate = "Crédito Imobiliário" 353→ case payment = "Inst. Pagamento" 354→ case other = "Outros" 355→ 356→ var icon: String { 357→ switch self { 358→ case .exchangeBroker: return "dollarsign.arrow.circlepath" 359→ case .directCredit: return "iphone" 360→ case .microfinance: return "person.badge.plus" 361→ case .leasing: return "car.fill" 362→ case .financing: return "creditcard.fill" 363→ case .stockBroker: return "chart.line.uptrend.xyaxis" 364→ case .distributor: return "square.grid.2x2" 365→ case .realEstate: return "house.fill" 366→ case .payment: return "banknote.fill" 367→ case .other: return "building.2.fill" 368→ } 369→ } 370→ 371→ var color: String { 372→ switch self { 373→ case .exchangeBroker: return "#10B981" 374→ case .directCredit: return "#8B5CF6" 375→ case .microfinance: return "#EC4899" 376→ case .leasing: return "#F59E0B" 377→ case .financing: return "#EF4444" 378→ case .stockBroker: return "#6366F1" 379→ case .distributor: return "#14B8A6" 380→ case .realEstate: return "#3B82F6" 381→ case .payment: return "#84CC16" 382→ case .other: return "#6B7280" 383→ } 384→ } 385→} 386→ 387→struct SociedadesResponse: Codable { 388→ let value: [Sociedade] 389→} 390→ 391→// MARK: - Unified Institution (Legacy compatibility) 392→ 393→struct InstitutionsResponse: Codable { 394→ let value: [Institution] 395→} 396→ 397→struct Institution: Codable, Identifiable, Hashable { 398→ var id: String { cnpj } 399→ let cnpj: String 400→ let name: String 401→ let shortName: String? 402→ let segment: String? 403→ let startDate: String? 404→ let city: String? 405→ let state: String? 406→ 407→ enum CodingKeys: String, CodingKey { 408→ case cnpj = "CNPJ" 409→ case name = "Nome" 410→ case shortName = "NomeReduzido" 411→ case segment = "Segmento" 412→ case startDate = "DataInicioFuncionamento" 413→ case city = "Municipio" 414→ case state = "UF" 415→ } 416→ 417→ var displayName: String { 418→ shortName ?? name 419→ } 420→ 421→ var formattedCNPJ: String { 422→ guard cnpj.count == 14 else { return cnpj } 423→ let chars = Array(cnpj) 424→ return "\(chars[0...1]).\(chars[2...4]).\(chars[5...7])/\(chars[8...11])-\(chars[12...13])" 425→ .replacingOccurrences(of: "ArraySlice", with: "") 426→ } 427→ 428→ var segmentType: InstitutionSegment { 429→ guard let segment = segment?.uppercased() else { return .other } 430→ 431→ if segment.contains("BANCO") || segment.contains("MÚLTIPLO") { 432→ return .bank 433→ } else if segment.contains("COOPERATIVA") { 434→ return .cooperative 435→ } else if segment.contains("FINANCEIRA") || segment.contains("CFI") { 436→ return .finance 437→ } else if segment.contains("SCD") { 438→ return .scd 439→ } else if segment.contains("SEP") { 440→ return .sep 441→ } else if segment.contains("PAGAMENTO") || segment.contains("IP") { 442→ return .paymentInstitution 443→ } else if segment.contains("CORRETORA") || segment.contains("DTVM") { 444→ return .broker 445→ } else if segment.contains("CONSÓRCIO") || segment.contains("CONSORCIO") { 446→ return .consortium 447→ } else { 448→ return .other 449→ } 450→ } 451→ 452→ var icon: String { 453→ segmentType.icon 454→ } 455→ 456→ var color: String { 457→ segmentType.color 458→ } 459→} 460→ 461→enum InstitutionSegment: String, CaseIterable { 462→ case bank = "Banco" 463→ case cooperative = "Cooperativa" 464→ case finance = "Financeira" 465→ case scd = "SCD (Fintech)" 466→ case sep = "SEP (P2P)" 467→ case paymentInstitution = "Inst. Pagamento" 468→ case broker = "Corretora" 469→ case consortium = "Consórcio" 470→ case other = "Outros" 471→ 472→ var icon: String { 473→ switch self { 474→ case .bank: return "building.columns.fill" 475→ case .cooperative: return "person.3.fill" 476→ case .finance: return "creditcard.fill" 477→ case .scd: return "iphone" 478→ case .sep: return "arrow.left.arrow.right" 479→ case .paymentInstitution: return "banknote.fill" 480→ case .broker: return "chart.line.uptrend.xyaxis" 481→ case .consortium: return "person.2.fill" 482→ case .other: return "building.2.fill" 483→ } 484→ } 485→ 486→ var color: String { 487→ switch self { 488→ case .bank: return "#3B82F6" 489→ case .cooperative: return "#10B981" 490→ case .finance: return "#F59E0B" 491→ case .scd: return "#8B5CF6" 492→ case .sep: return "#EC4899" 493→ case .paymentInstitution: return "#14B8A6" 494→ case .broker: return "#6366F1" 495→ case .consortium: return "#EF4444" 496→ case .other: return "#6B7280" 497→ } 498→ } 499→} 500→ 501→// MARK: - PIX Statistics 502→ 503→// Daily PIX settlements 504→struct PIXDailyResponse: Codable { 505→ let value: [PIXDaily] 506→} 507→ 508→struct PIXDaily: Codable, Identifiable { 509→ var id: String { date } 510→ let date: String 511→ let quantity: Int64 512→ let primaryChannel: Int64? 513→ let secondaryChannel: Int64? 514→ let total: Double 515→ let average: Double 516→ 517→ enum CodingKeys: String, CodingKey { 518→ case date = "Data" 519→ case quantity = "Quantidade" 520→ case primaryChannel = "CanalPrimario" 521→ case secondaryChannel = "CanalSecundario" 522→ case total = "Total" 523→ case average = "Media" 524→ } 525→ 526→ var parsedDate: Date? { 527→ let formatter = DateFormatter() 528→ formatter.dateFormat = "yyyy-MM-dd" 529→ return formatter.date(from: date) 530→ } 531→ 532→ var formattedDate: String { 533→ guard let parsed = parsedDate else { return date } 534→ let formatter = DateFormatter() 535→ formatter.dateFormat = "dd/MM/yyyy" 536→ return formatter.string(from: parsed) 537→ } 538→ 539→ var formattedTotal: String { 540→ // Total is in thousands (R$ mil) 541→ let billions = total / 1_000_000 542→ if billions >= 1 { 543→ return String(format: "R$ %.1f bi", billions) 544→ } else { 545→ let millions = total / 1_000 546→ return String(format: "R$ %.1f mi", millions) 547→ } 548→ } 549→ 550→ var formattedQuantity: String { 551→ let millions = Double(quantity) / 1_000_000 552→ return String(format: "%.1f M", millions) 553→ } 554→ 555→ var formattedAverage: String { 556→ return String(format: "R$ %.2f", average) 557→ } 558→} 559→ 560→// Hourly PIX averages 561→struct PIXIntradayResponse: Codable { 562→ let value: [PIXIntraday] 563→} 564→ 565→struct PIXIntraday: Codable, Identifiable { 566→ var id: String { time } 567→ let time: String 568→ let averageQuantity: Int64 569→ let averageTotal: Double 570→ 571→ enum CodingKeys: String, CodingKey { 572→ case time = "Horario" 573→ case averageQuantity = "QuantidadeMedia" 574→ case averageTotal = "TotalMedio" 575→ } 576→ 577→ var formattedQuantity: String { 578→ let millions = Double(averageQuantity) / 1_000_000 579→ return String(format: "%.2f M", millions) 580→ } 581→ 582→ var formattedTotal: String { 583→ let millions = averageTotal / 1_000_000 584→ return String(format: "R$ %.0f M", millions) 585→ } 586→} 587→ 588→// PIX availability 589→struct PIXAvailabilityResponse: Codable { 590→ let value: [PIXAvailability] 591→} 592→ 593→struct PIXAvailability: Codable, Identifiable { 594→ var id: String { date } 595→ let date: String 596→ let index: Double 597→ let minimumRequired: Double 598→ 599→ enum CodingKeys: String, CodingKey { 600→ case date = "DataBase" 601→ case index = "Indice" 602→ case minimumRequired = "MinimoNormativo" 603→ } 604→ 605→ var formattedIndex: String { 606→ return String(format: "%.2f%%", index) 607→ } 608→ 609→ var parsedDate: Date? { 610→ let formatter = DateFormatter() 611→ formatter.dateFormat = "yyyy-MM-dd" 612→ return formatter.date(from: date) 613→ } 614→ 615→ var formattedDate: String { 616→ guard let parsed = parsedDate else { return date } 617→ let formatter = DateFormatter() 618→ formatter.dateFormat = "MMM/yy" 619→ return formatter.string(from: parsed) 620→ } 621→} 622→ 623→// PIX account remuneration 624→struct PIXRemunerationResponse: Codable { 625→ let value: [PIXRemuneration] 626→} 627→ 628→struct PIXRemuneration: Codable, Identifiable { 629→ var id: String { date } 630→ let date: String 631→ let baseIF: Double 632→ let remuneratedIF: Double 633→ let baseTotal: Double 634→ let remuneratedTotal: Double 635→ let selicAnnual: Double 636→ 637→ enum CodingKeys: String, CodingKey { 638→ case date = "DataBase" 639→ case baseIF = "BaseRemuneracaoIF" 640→ case remuneratedIF = "ValorRemuneradoIF" 641→ case baseTotal = "BaseRemuneracaoTotal" 642→ case remuneratedTotal = "ValorRemuneradoTotal" 643→ case selicAnnual = "SelicAnual" 644→ } 645→ 646→ var formattedBaseTotal: String { 647→ let billions = baseTotal / 1_000_000_000 648→ return String(format: "R$ %.1f bi", billions) 649→ } 650→ 651→ var formattedSelic: String { 652→ return String(format: "%.2f%%", selicAnnual * 100) 653→ } 654→} 655→ 656→// Aggregated PIX stats 657→struct PIXStats { 658→ var dailyData: [PIXDaily] = [] 659→ var intradayData: [PIXIntraday] = [] 660→ var availabilityData: [PIXAvailability] = [] 661→ var remunerationData: [PIXRemuneration] = [] 662→ 663→ var latestDaily: PIXDaily? { 664→ dailyData.sorted { ($0.parsedDate ?? Date.distantPast) > ($1.parsedDate ?? Date.distantPast) }.first 665→ } 666→ 667→ var totalTransactionsToday: Int64 { 668→ latestDaily?.quantity ?? 0 669→ } 670→ 671→ var totalValueToday: Double { 672→ latestDaily?.total ?? 0 673→ } 674→ 675→ var averageAvailability: Double { 676→ guard !availabilityData.isEmpty else { return 0 } 677→ return availabilityData.reduce(0) { $0 + $1.index } / Double(availabilityData.count) 678→ } 679→ 680→ var last30DaysData: [PIXDaily] { 681→ let sorted = dailyData.sorted { ($0.parsedDate ?? Date.distantPast) > ($1.parsedDate ?? Date.distantPast) } 682→ return Array(sorted.prefix(30)).reversed() 683→ } 684→} 685→ 686→// Legacy compatibility 687→struct PIXStatisticsResponse: Codable { 688→ let value: [PIXStatistic] 689→} 690→ 691→struct PIXStatistic: Codable, Identifiable { 692→ var id: String { "\(date)-\(type)" } 693→ let date: String 694→ let type: String 695→ let quantity: Int64 696→ let value: Double 697→ 698→ enum CodingKeys: String, CodingKey { 699→ case date = "Data" 700→ case type = "Tipo" 701→ case quantity = "Quantidade" 702→ case value = "Valor" 703→ } 704→ 705→ var formattedValue: String { 706→ let formatter = NumberFormatter() 707→ formatter.numberStyle = .currency 708→ formatter.currencyCode = "BRL" 709→ return formatter.string(from: NSNumber(value: value)) ?? "R$ 0" 710→ } 711→ 712→ var formattedQuantity: String { 713→ let formatter = NumberFormatter() 714→ formatter.numberStyle = .decimal 715→ formatter.groupingSeparator = "." 716→ return formatter.string(from: NSNumber(value: quantity)) ?? "0" 717→ } 718→} 719→ 720→// MARK: - Market Expectations (Focus) 721→ 722→// SELIC Expectations by COPOM Meeting 723→struct SelicExpectationResponse: Codable { 724→ let value: [SelicExpectation] 725→} 726→ 727→struct SelicExpectation: Codable, Identifiable { 728→ var id: String { "\(meeting)-\(baseDate)-\(baseCalculo ?? 0)" } 729→ let indicator: String 730→ let baseDate: String 731→ let meeting: String 732→ let mean: Double 733→ let median: Double 734→ let standardDeviation: Double? 735→ let min: Double 736→ let max: Double 737→ let respondents: Int? 738→ let baseCalculo: Int? 739→ 740→ enum CodingKeys: String, CodingKey { 741→ case indicator = "Indicador" 742→ case baseDate = "Data" 743→ case meeting = "Reuniao" 744→ case mean = "Media" 745→ case median = "Mediana" 746→ case standardDeviation = "DesvioPadrao" 747→ case min = "Minimo" 748→ case max = "Maximo" 749→ case respondents = "numeroRespondentes" 750→ case baseCalculo = "baseCalculo" 751→ } 752→ 753→ var parsedDate: Date? { 754→ let formatter = DateFormatter() 755→ formatter.dateFormat = "yyyy-MM-dd" 756→ return formatter.date(from: baseDate) 757→ } 758→ 759→ var formattedDate: String { 760→ guard let date = parsedDate else { return baseDate } 761→ let formatter = DateFormatter() 762→ formatter.dateFormat = "dd/MM/yyyy" 763→ return formatter.string(from: date) 764→ } 765→ 766→ var meetingNumber: Int? { 767→ // Format: "R8/2027" -> 8 768→ guard let range = meeting.range(of: "R") else { return nil } 769→ let afterR = meeting[range.upperBound...] 770→ guard let slashIndex = afterR.firstIndex(of: "/") else { return nil } 771→ return Int(afterR[.. 2027 776→ guard let slashIndex = meeting.firstIndex(of: "/") else { return nil } 777→ return Int(meeting[meeting.index(after: slashIndex)...]) 778→ } 779→ 780→ var formattedMeeting: String { 781→ // "R8/2027" -> "Reunião 8/2027" 782→ meeting.replacingOccurrences(of: "R", with: "Reunião ") 783→ } 784→} 785→ 786→// Inflation Expectations (IPCA 12 months) 787→struct InflationExpectationResponse: Codable { 788→ let value: [InflationExpectation] 789→} 790→ 791→struct InflationExpectation: Codable, Identifiable { 792→ var id: String { "\(indicator)-\(baseDate)-\(smoothed)-\(baseCalculo ?? 0)" } 793→ let indicator: String 794→ let baseDate: String 795→ let smoothed: String 796→ let mean: Double 797→ let median: Double 798→ let standardDeviation: Double? 799→ let min: Double 800→ let max: Double 801→ let respondents: Int? 802→ let baseCalculo: Int? 803→ 804→ enum CodingKeys: String, CodingKey { 805→ case indicator = "Indicador" 806→ case baseDate = "Data" 807→ case smoothed = "Suavizada" 808→ case mean = "Media" 809→ case median = "Mediana" 810→ case standardDeviation = "DesvioPadrao" 811→ case min = "Minimo" 812→ case max = "Maximo" 813→ case respondents = "numeroRespondentes" 814→ case baseCalculo = "baseCalculo" 815→ } 816→ 817→ var isSmoothed: Bool { smoothed == "S" } 818→ 819→ var parsedDate: Date? { 820→ let formatter = DateFormatter() 821→ formatter.dateFormat = "yyyy-MM-dd" 822→ return formatter.date(from: baseDate) 823→ } 824→ 825→ var formattedDate: String { 826→ guard let date = parsedDate else { return baseDate } 827→ let formatter = DateFormatter() 828→ formatter.dateFormat = "dd/MM/yyyy" 829→ return formatter.string(from: date) 830→ } 831→} 832→ 833→// Annual Expectations (IPCA, PIB, Câmbio) 834→struct AnnualExpectationResponse: Codable { 835→ let value: [AnnualExpectation] 836→} 837→ 838→struct AnnualExpectation: Codable, Identifiable { 839→ var id: String { "\(indicator)-\(referenceYear)-\(baseDate)-\(baseCalculo ?? 0)" } 840→ let indicator: String 841→ let indicatorDetail: String? 842→ let baseDate: String 843→ let referenceYear: String 844→ let mean: Double 845→ let median: Double 846→ let standardDeviation: Double? 847→ let min: Double 848→ let max: Double 849→ let respondents: Int? 850→ let baseCalculo: Int? 851→ 852→ enum CodingKeys: String, CodingKey { 853→ case indicator = "Indicador" 854→ case indicatorDetail = "IndicadorDetalhe" 855→ case baseDate = "Data" 856→ case referenceYear = "DataReferencia" 857→ case mean = "Media" 858→ case median = "Mediana" 859→ case standardDeviation = "DesvioPadrao" 860→ case min = "Minimo" 861→ case max = "Maximo" 862→ case respondents = "numeroRespondentes" 863→ case baseCalculo = "baseCalculo" 864→ } 865→ 866→ var parsedDate: Date? { 867→ let formatter = DateFormatter() 868→ formatter.dateFormat = "yyyy-MM-dd" 869→ return formatter.date(from: baseDate) 870→ } 871→ 872→ var formattedDate: String { 873→ guard let date = parsedDate else { return baseDate } 874→ let formatter = DateFormatter() 875→ formatter.dateFormat = "dd/MM/yyyy" 876→ return formatter.string(from: date) 877→ } 878→} 879→ 880→// Aggregated Focus Stats 881→struct FocusStats { 882→ var selicExpectations: [SelicExpectation] = [] 883→ var ipcaExpectations: [InflationExpectation] = [] 884→ var igpmExpectations: [InflationExpectation] = [] 885→ var annualIPCA: [AnnualExpectation] = [] 886→ var annualIGPM: [AnnualExpectation] = [] 887→ var annualPIB: [AnnualExpectation] = [] 888→ var annualCambio: [AnnualExpectation] = [] 889→ 890→ // Latest SELIC expectation (Top 5 institutions, next meeting) 891→ var latestSelic: SelicExpectation? { 892→ // Get baseCalculo=1 (Top 5) for the nearest meeting 893→ selicExpectations 894→ .filter { $0.baseCalculo == 1 } 895→ .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) } 896→ .first 897→ } 898→ 899→ // Latest IPCA 12m expectation (Top 5) 900→ var latestIPCA12m: InflationExpectation? { 901→ ipcaExpectations 902→ .filter { $0.baseCalculo == 1 && $0.indicator == "IPCA" && !$0.isSmoothed } 903→ .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) } 904→ .first 905→ } 906→ 907→ // Latest IGP-M 12m expectation (Top 5) 908→ var latestIGPM12m: InflationExpectation? { 909→ igpmExpectations 910→ .filter { $0.baseCalculo == 1 && $0.indicator == "IGP-M" && !$0.isSmoothed } 911→ .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) } 912→ .first 913→ } 914→ 915→ // Latest annual IPCA for current year 916→ var latestIPCAYear: AnnualExpectation? { 917→ let currentYear = Calendar.current.component(.year, from: Date()) 918→ return annualIPCA 919→ .filter { $0.baseCalculo == 0 && $0.referenceYear == "\(currentYear)" } 920→ .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) } 921→ .first 922→ } 923→ 924→ // Latest annual IGP-M for current year 925→ var latestIGPMYear: AnnualExpectation? { 926→ let currentYear = Calendar.current.component(.year, from: Date()) 927→ return annualIGPM 928→ .filter { $0.baseCalculo == 0 && $0.referenceYear == "\(currentYear)" } 929→ .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) } 930→ .first 931→ } 932→ 933→ // Latest PIB for current year 934→ var latestPIBYear: AnnualExpectation? { 935→ let currentYear = Calendar.current.component(.year, from: Date()) 936→ return annualPIB 937→ .filter { $0.baseCalculo == 0 && $0.referenceYear == "\(currentYear)" } 938→ .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) } 939→ .first 940→ } 941→ 942→ // SELIC expectations grouped by meeting 943→ var selicByMeeting: [String: SelicExpectation] { 944→ var result: [String: SelicExpectation] = [:] 945→ // Get latest date for each meeting (baseCalculo=0 for all institutions) 946→ for exp in selicExpectations.filter({ $0.baseCalculo == 0 }) { 947→ if result[exp.meeting] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.meeting]?.parsedDate ?? .distantPast) { 948→ result[exp.meeting] = exp 949→ } 950→ } 951→ return result 952→ } 953→ 954→ // IPCA annual expectations by year 955→ var ipcaByYear: [String: AnnualExpectation] { 956→ var result: [String: AnnualExpectation] = [:] 957→ for exp in annualIPCA.filter({ $0.baseCalculo == 0 }) { 958→ if result[exp.referenceYear] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.referenceYear]?.parsedDate ?? .distantPast) { 959→ result[exp.referenceYear] = exp 960→ } 961→ } 962→ return result 963→ } 964→ 965→ // IGP-M annual expectations by year 966→ var igpmByYear: [String: AnnualExpectation] { 967→ var result: [String: AnnualExpectation] = [:] 968→ for exp in annualIGPM.filter({ $0.baseCalculo == 0 }) { 969→ if result[exp.referenceYear] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.referenceYear]?.parsedDate ?? .distantPast) { 970→ result[exp.referenceYear] = exp 971→ } 972→ } 973→ return result 974→ } 975→ 976→ // PIB annual expectations by year 977→ var pibByYear: [String: AnnualExpectation] { 978→ var result: [String: AnnualExpectation] = [:] 979→ for exp in annualPIB.filter({ $0.baseCalculo == 0 }) { 980→ if result[exp.referenceYear] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.referenceYear]?.parsedDate ?? .distantPast) { 981→ result[exp.referenceYear] = exp 982→ } 983→ } 984→ return result 985→ } 986→} 987→ 988→// Legacy compatibility 989→struct ExpectationsResponse: Codable { 990→ let value: [MarketExpectation] 991→} 992→ 993→struct MarketExpectation: Codable, Identifiable { 994→ var id: String { "\(indicator)-\(referenceDate)-\(baseDate)" } 995→ let indicator: String 996→ let referenceDate: String 997→ let baseDate: String 998→ let mean: Double 999→ let median: Double 1000→ let min: Double 1001→ let max: Double 1002→ let standardDeviation: Double? 1003→ let respondents: Int? 1004→ 1005→ enum CodingKeys: String, CodingKey { 1006→ case indicator = "Indicador" 1007→ case referenceDate = "DataReferencia" 1008→ case baseDate = "Data" 1009→ case mean = "Media" 1010→ case median = "Mediana" 1011→ case min = "Minimo" 1012→ case max = "Maximo" 1013→ case standardDeviation = "DesvioPadrao" 1014→ case respondents = "numeroRespondentes" 1015→ } 1016→} 1017→ 1018→// MARK: - Statistics Summary 1019→ 1020→struct InstitutionStats { 1021→ var totalBanks: Int = 0 1022→ var totalCooperatives: Int = 0 1023→ var totalConsortia: Int = 0 1024→ var totalSociedades: Int = 0 1025→ 1026→ var total: Int { 1027→ totalBanks + totalCooperatives + totalConsortia + totalSociedades 1028→ } 1029→ 1030→ var stateDistribution: [String: Int] = [:] 1031→} 1032→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.