     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[..<slashIndex])
   772→    }
   773→
   774→    var meetingYear: Int? {
   775→        // Format: "R8/2027" -> 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→

<system-reminder>
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.
</system-reminder>

import Foundation

// MARK: - Institution Types

enum InstitutionType: String, CaseIterable, Identifiable {
    case banks = "Bancos"
    case cooperatives = "Cooperativas"
    case consortia = "Consórcios"
    case sociedades = "Sociedades"

    var id: String { rawValue }

    var icon: String {
        switch self {
        case .banks: return "building.columns.fill"
        case .cooperatives: return "person.3.fill"
        case .consortia: return "person.2.fill"
        case .sociedades: return "building.2.fill"
        }
    }

    var color: String {
        switch self {
        case .banks: return "#3B82F6"
        case .cooperatives: return "#10B981"
        case .consortia: return "#F59E0B"
        case .sociedades: return "#8B5CF6"
        }
    }
}

// MARK: - Bank Model

struct Bank: Codable, Identifiable, Hashable {
    var id: String { cnpj }

    let cnpj: String
    let name: String
    let segment: String?
    let address: String?
    let complement: String?
    let neighborhood: String?
    let cep: String?
    let city: String?
    let state: String?
    let ddd: String?
    let phone: String?
    let hasCommercialPortfolio: String?
    let email: String?
    let website: String?
    let ibgeCode: String?

    enum CodingKeys: String, CodingKey {
        case cnpj = "CNPJ"
        case name = "NOME_INSTITUICAO"
        case segment = "SEGMENTO"
        case address = "ENDERECO"
        case complement = "COMPLEMENTO"
        case neighborhood = "BAIRRO"
        case cep = "CEP"
        case city = "MUNICIPIO"
        case state = "UF"
        case ddd = "DDD"
        case phone = "TELEFONE"
        case hasCommercialPortfolio = "CARTEIRA_COMERCIAL"
        case email = "E_MAIL"
        case website = "SITIO_NA_INTERNET"
        case ibgeCode = "MUNICIPIO_IBGE"
    }

    var displayName: String {
        name
    }

    var formattedPhone: String {
        guard let ddd = ddd, let phone = phone else { return "--" }
        return "(\(ddd)) \(phone)"
    }

    var formattedCNPJ: String {
        guard cnpj.count >= 8 else { return cnpj }
        let chars = Array(cnpj)
        if chars.count == 8 {
            return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))"
        }
        return cnpj
    }

    var fullAddress: String {
        var parts: [String] = []
        if let addr = address { parts.append(addr) }
        if let comp = complement { parts.append(comp) }
        if let bairro = neighborhood { parts.append(bairro) }
        if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") }
        if let cepVal = cep { parts.append("CEP: \(cepVal)") }
        return parts.joined(separator: ", ")
    }
}

struct BanksResponse: Codable {
    let value: [Bank]
}

// MARK: - Cooperative Model

struct Cooperative: Codable, Identifiable, Hashable {
    var id: String { cnpj }

    let cnpj: String
    let name: String
    let address: String?
    let complement: String?
    let neighborhood: String?
    let cep: String?
    let city: String?
    let state: String?
    let ddd: String?
    let phone: String?
    let coopClass: String?
    let association: String?
    let category: String?
    let affiliation: String?
    let email: String?
    let website: String?
    let ibgeCode: String?

    enum CodingKeys: String, CodingKey {
        case cnpj = "CNPJ"
        case name = "NOME_INSTITUICAO"
        case address = "ENDERECO"
        case complement = "COMPLEMENTO"
        case neighborhood = "BAIRRO"
        case cep = "CEP"
        case city = "MUNICIPIO"
        case state = "UF"
        case ddd = "DDD"
        case phone = "TELEFONE"
        case coopClass = "CLASSE"
        case association = "ASSOCIACAO"
        case category = "CATEGCOOPSING"
        case affiliation = "FILIACAO"
        case email = "E_MAIL"
        case website = "SITIO_NA_INTERNET"
        case ibgeCode = "MUNICIPIO_IBGE"
    }

    var displayName: String {
        // Shorten long names
        if name.count > 60 {
            return String(name.prefix(57)) + "..."
        }
        return name
    }

    var formattedPhone: String {
        guard let ddd = ddd, let phone = phone else { return "--" }
        return "(\(ddd)) \(phone)"
    }

    var formattedCNPJ: String {
        guard cnpj.count >= 8 else { return cnpj }
        let chars = Array(cnpj)
        if chars.count == 8 {
            return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))"
        }
        return cnpj
    }

    var fullAddress: String {
        var parts: [String] = []
        if let addr = address { parts.append(addr) }
        if let comp = complement { parts.append(comp) }
        if let bairro = neighborhood { parts.append(bairro) }
        if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") }
        if let cepVal = cep { parts.append("CEP: \(cepVal)") }
        return parts.joined(separator: ", ")
    }
}

struct CooperativesResponse: Codable {
    let value: [Cooperative]
}

// MARK: - Consortium Model

struct Consortium: Codable, Identifiable, Hashable {
    var id: String { cnpj }

    let cnpj: String
    let name: String
    let address: String?
    let complement: String?
    let neighborhood: String?
    let cep: String?
    let city: String?
    let state: String?
    let ddd: String?
    let phone: String?
    let email: String?
    let website: String?
    let ibgeCode: String?

    enum CodingKeys: String, CodingKey {
        case cnpj = "CNPJ"
        case name = "NOME_INSTITUICAO"
        case address = "ENDERECO"
        case complement = "COMPLEMENTO"
        case neighborhood = "BAIRRO"
        case cep = "CEP"
        case city = "MUNICIPIO"
        case state = "UF"
        case ddd = "DDD"
        case phone = "TELEFONE"
        case email = "E_MAIL"
        case website = "SITIO_NA_INTERNET"
        case ibgeCode = "MUNICIPIO_IBGE"
    }

    var displayName: String {
        name
    }

    var formattedPhone: String {
        guard let ddd = ddd, let phone = phone else { return "--" }
        return "(\(ddd)) \(phone)"
    }

    var formattedCNPJ: String {
        guard cnpj.count >= 8 else { return cnpj }
        let chars = Array(cnpj)
        if chars.count == 8 {
            return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))"
        }
        return cnpj
    }

    var fullAddress: String {
        var parts: [String] = []
        if let addr = address { parts.append(addr) }
        if let comp = complement { parts.append(comp) }
        if let bairro = neighborhood { parts.append(bairro) }
        if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") }
        if let cepVal = cep { parts.append("CEP: \(cepVal)") }
        return parts.joined(separator: ", ")
    }
}

struct ConsortiaResponse: Codable {
    let value: [Consortium]
}

// MARK: - Sociedade Model

struct Sociedade: Codable, Identifiable, Hashable {
    var id: String { cnpj }

    let cnpj: String
    let name: String
    let segment: String?
    let address: String?
    let complement: String?
    let neighborhood: String?
    let cep: String?
    let city: String?
    let state: String?
    let ddd: String?
    let phone: String?
    let email: String?
    let website: String?
    let ibgeCode: String?

    enum CodingKeys: String, CodingKey {
        case cnpj = "CNPJ"
        case name = "NOME_INSTITUICAO"
        case segment = "SEGMENTO"
        case address = "ENDERECO"
        case complement = "COMPLEMENTO"
        case neighborhood = "BAIRRO"
        case cep = "CEP"
        case city = "MUNICIPIO"
        case state = "UF"
        case ddd = "DDD"
        case phone = "TELEFONE"
        case email = "E_MAIL"
        case website = "SITIO_NA_INTERNET"
        case ibgeCode = "MUNICIPIO_IBGE"
    }

    var displayName: String {
        name
    }

    var formattedPhone: String {
        guard let ddd = ddd, let phone = phone else { return "--" }
        return "(\(ddd)) \(phone)"
    }

    var formattedCNPJ: String {
        guard cnpj.count >= 8 else { return cnpj }
        let chars = Array(cnpj)
        if chars.count == 8 {
            return "\(String(chars[0...1])).\(String(chars[2...4])).\(String(chars[5...7]))"
        }
        return cnpj
    }

    var fullAddress: String {
        var parts: [String] = []
        if let addr = address { parts.append(addr) }
        if let comp = complement { parts.append(comp) }
        if let bairro = neighborhood { parts.append(bairro) }
        if let cidade = city, let uf = state { parts.append("\(cidade)/\(uf)") }
        if let cepVal = cep { parts.append("CEP: \(cepVal)") }
        return parts.joined(separator: ", ")
    }

    var segmentType: SociedadeSegment {
        guard let seg = segment?.uppercased() else { return .other }

        if seg.contains("CORRETORA DE CÂMBIO") {
            return .exchangeBroker
        } else if seg.contains("CRÉDITO DIRETO") || seg.contains("SCD") {
            return .directCredit
        } else if seg.contains("MICROEMPREENDEDOR") {
            return .microfinance
        } else if seg.contains("ARRENDAMENTO") {
            return .leasing
        } else if seg.contains("FINANCIAMENTO") {
            return .financing
        } else if seg.contains("CORRETORA") || seg.contains("TVM") {
            return .stockBroker
        } else if seg.contains("DISTRIBUIDORA") {
            return .distributor
        } else if seg.contains("CRÉDITO IMOBILIÁRIO") {
            return .realEstate
        } else if seg.contains("PAGAMENTO") {
            return .payment
        } else {
            return .other
        }
    }
}

enum SociedadeSegment: String, CaseIterable {
    case exchangeBroker = "Corretora de Câmbio"
    case directCredit = "SCD (Fintech)"
    case microfinance = "Microcrédito"
    case leasing = "Arrendamento"
    case financing = "Financeira"
    case stockBroker = "Corretora TVM"
    case distributor = "Distribuidora TVM"
    case realEstate = "Crédito Imobiliário"
    case payment = "Inst. Pagamento"
    case other = "Outros"

    var icon: String {
        switch self {
        case .exchangeBroker: return "dollarsign.arrow.circlepath"
        case .directCredit: return "iphone"
        case .microfinance: return "person.badge.plus"
        case .leasing: return "car.fill"
        case .financing: return "creditcard.fill"
        case .stockBroker: return "chart.line.uptrend.xyaxis"
        case .distributor: return "square.grid.2x2"
        case .realEstate: return "house.fill"
        case .payment: return "banknote.fill"
        case .other: return "building.2.fill"
        }
    }

    var color: String {
        switch self {
        case .exchangeBroker: return "#10B981"
        case .directCredit: return "#8B5CF6"
        case .microfinance: return "#EC4899"
        case .leasing: return "#F59E0B"
        case .financing: return "#EF4444"
        case .stockBroker: return "#6366F1"
        case .distributor: return "#14B8A6"
        case .realEstate: return "#3B82F6"
        case .payment: return "#84CC16"
        case .other: return "#6B7280"
        }
    }
}

struct SociedadesResponse: Codable {
    let value: [Sociedade]
}

// MARK: - Unified Institution (Legacy compatibility)

struct InstitutionsResponse: Codable {
    let value: [Institution]
}

struct Institution: Codable, Identifiable, Hashable {
    var id: String { cnpj }
    let cnpj: String
    let name: String
    let shortName: String?
    let segment: String?
    let startDate: String?
    let city: String?
    let state: String?

    enum CodingKeys: String, CodingKey {
        case cnpj = "CNPJ"
        case name = "Nome"
        case shortName = "NomeReduzido"
        case segment = "Segmento"
        case startDate = "DataInicioFuncionamento"
        case city = "Municipio"
        case state = "UF"
    }

    var displayName: String {
        shortName ?? name
    }

    var formattedCNPJ: String {
        guard cnpj.count == 14 else { return cnpj }
        let chars = Array(cnpj)
        return "\(chars[0...1]).\(chars[2...4]).\(chars[5...7])/\(chars[8...11])-\(chars[12...13])"
            .replacingOccurrences(of: "ArraySlice", with: "")
    }

    var segmentType: InstitutionSegment {
        guard let segment = segment?.uppercased() else { return .other }

        if segment.contains("BANCO") || segment.contains("MÚLTIPLO") {
            return .bank
        } else if segment.contains("COOPERATIVA") {
            return .cooperative
        } else if segment.contains("FINANCEIRA") || segment.contains("CFI") {
            return .finance
        } else if segment.contains("SCD") {
            return .scd
        } else if segment.contains("SEP") {
            return .sep
        } else if segment.contains("PAGAMENTO") || segment.contains("IP") {
            return .paymentInstitution
        } else if segment.contains("CORRETORA") || segment.contains("DTVM") {
            return .broker
        } else if segment.contains("CONSÓRCIO") || segment.contains("CONSORCIO") {
            return .consortium
        } else {
            return .other
        }
    }

    var icon: String {
        segmentType.icon
    }

    var color: String {
        segmentType.color
    }
}

enum InstitutionSegment: String, CaseIterable {
    case bank = "Banco"
    case cooperative = "Cooperativa"
    case finance = "Financeira"
    case scd = "SCD (Fintech)"
    case sep = "SEP (P2P)"
    case paymentInstitution = "Inst. Pagamento"
    case broker = "Corretora"
    case consortium = "Consórcio"
    case other = "Outros"

    var icon: String {
        switch self {
        case .bank: return "building.columns.fill"
        case .cooperative: return "person.3.fill"
        case .finance: return "creditcard.fill"
        case .scd: return "iphone"
        case .sep: return "arrow.left.arrow.right"
        case .paymentInstitution: return "banknote.fill"
        case .broker: return "chart.line.uptrend.xyaxis"
        case .consortium: return "person.2.fill"
        case .other: return "building.2.fill"
        }
    }

    var color: String {
        switch self {
        case .bank: return "#3B82F6"
        case .cooperative: return "#10B981"
        case .finance: return "#F59E0B"
        case .scd: return "#8B5CF6"
        case .sep: return "#EC4899"
        case .paymentInstitution: return "#14B8A6"
        case .broker: return "#6366F1"
        case .consortium: return "#EF4444"
        case .other: return "#6B7280"
        }
    }
}

// MARK: - PIX Statistics

// Daily PIX settlements
struct PIXDailyResponse: Codable {
    let value: [PIXDaily]
}

struct PIXDaily: Codable, Identifiable {
    var id: String { date }
    let date: String
    let quantity: Int64
    let primaryChannel: Int64?
    let secondaryChannel: Int64?
    let total: Double
    let average: Double

    enum CodingKeys: String, CodingKey {
        case date = "Data"
        case quantity = "Quantidade"
        case primaryChannel = "CanalPrimario"
        case secondaryChannel = "CanalSecundario"
        case total = "Total"
        case average = "Media"
    }

    var parsedDate: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: date)
    }

    var formattedDate: String {
        guard let parsed = parsedDate else { return date }
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.string(from: parsed)
    }

    var formattedTotal: String {
        // Total is in thousands (R$ mil)
        let billions = total / 1_000_000
        if billions >= 1 {
            return String(format: "R$ %.1f bi", billions)
        } else {
            let millions = total / 1_000
            return String(format: "R$ %.1f mi", millions)
        }
    }

    var formattedQuantity: String {
        let millions = Double(quantity) / 1_000_000
        return String(format: "%.1f M", millions)
    }

    var formattedAverage: String {
        return String(format: "R$ %.2f", average)
    }
}

// Hourly PIX averages
struct PIXIntradayResponse: Codable {
    let value: [PIXIntraday]
}

struct PIXIntraday: Codable, Identifiable {
    var id: String { time }
    let time: String
    let averageQuantity: Int64
    let averageTotal: Double

    enum CodingKeys: String, CodingKey {
        case time = "Horario"
        case averageQuantity = "QuantidadeMedia"
        case averageTotal = "TotalMedio"
    }

    var formattedQuantity: String {
        let millions = Double(averageQuantity) / 1_000_000
        return String(format: "%.2f M", millions)
    }

    var formattedTotal: String {
        let millions = averageTotal / 1_000_000
        return String(format: "R$ %.0f M", millions)
    }
}

// PIX availability
struct PIXAvailabilityResponse: Codable {
    let value: [PIXAvailability]
}

struct PIXAvailability: Codable, Identifiable {
    var id: String { date }
    let date: String
    let index: Double
    let minimumRequired: Double

    enum CodingKeys: String, CodingKey {
        case date = "DataBase"
        case index = "Indice"
        case minimumRequired = "MinimoNormativo"
    }

    var formattedIndex: String {
        return String(format: "%.2f%%", index)
    }

    var parsedDate: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: date)
    }

    var formattedDate: String {
        guard let parsed = parsedDate else { return date }
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM/yy"
        return formatter.string(from: parsed)
    }
}

// PIX account remuneration
struct PIXRemunerationResponse: Codable {
    let value: [PIXRemuneration]
}

struct PIXRemuneration: Codable, Identifiable {
    var id: String { date }
    let date: String
    let baseIF: Double
    let remuneratedIF: Double
    let baseTotal: Double
    let remuneratedTotal: Double
    let selicAnnual: Double

    enum CodingKeys: String, CodingKey {
        case date = "DataBase"
        case baseIF = "BaseRemuneracaoIF"
        case remuneratedIF = "ValorRemuneradoIF"
        case baseTotal = "BaseRemuneracaoTotal"
        case remuneratedTotal = "ValorRemuneradoTotal"
        case selicAnnual = "SelicAnual"
    }

    var formattedBaseTotal: String {
        let billions = baseTotal / 1_000_000_000
        return String(format: "R$ %.1f bi", billions)
    }

    var formattedSelic: String {
        return String(format: "%.2f%%", selicAnnual * 100)
    }
}

// Aggregated PIX stats
struct PIXStats {
    var dailyData: [PIXDaily] = []
    var intradayData: [PIXIntraday] = []
    var availabilityData: [PIXAvailability] = []
    var remunerationData: [PIXRemuneration] = []

    var latestDaily: PIXDaily? {
        dailyData.sorted { ($0.parsedDate ?? Date.distantPast) > ($1.parsedDate ?? Date.distantPast) }.first
    }

    var totalTransactionsToday: Int64 {
        latestDaily?.quantity ?? 0
    }

    var totalValueToday: Double {
        latestDaily?.total ?? 0
    }

    var averageAvailability: Double {
        guard !availabilityData.isEmpty else { return 0 }
        return availabilityData.reduce(0) { $0 + $1.index } / Double(availabilityData.count)
    }

    var last30DaysData: [PIXDaily] {
        let sorted = dailyData.sorted { ($0.parsedDate ?? Date.distantPast) > ($1.parsedDate ?? Date.distantPast) }
        return Array(sorted.prefix(30)).reversed()
    }
}

// Legacy compatibility
struct PIXStatisticsResponse: Codable {
    let value: [PIXStatistic]
}

struct PIXStatistic: Codable, Identifiable {
    var id: String { "\(date)-\(type)" }
    let date: String
    let type: String
    let quantity: Int64
    let value: Double

    enum CodingKeys: String, CodingKey {
        case date = "Data"
        case type = "Tipo"
        case quantity = "Quantidade"
        case value = "Valor"
    }

    var formattedValue: String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.currencyCode = "BRL"
        return formatter.string(from: NSNumber(value: value)) ?? "R$ 0"
    }

    var formattedQuantity: String {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.groupingSeparator = "."
        return formatter.string(from: NSNumber(value: quantity)) ?? "0"
    }
}

// MARK: - Market Expectations (Focus)

// SELIC Expectations by COPOM Meeting
struct SelicExpectationResponse: Codable {
    let value: [SelicExpectation]
}

struct SelicExpectation: Codable, Identifiable {
    var id: String { "\(meeting)-\(baseDate)-\(baseCalculo ?? 0)" }
    let indicator: String
    let baseDate: String
    let meeting: String
    let mean: Double
    let median: Double
    let standardDeviation: Double?
    let min: Double
    let max: Double
    let respondents: Int?
    let baseCalculo: Int?

    enum CodingKeys: String, CodingKey {
        case indicator = "Indicador"
        case baseDate = "Data"
        case meeting = "Reuniao"
        case mean = "Media"
        case median = "Mediana"
        case standardDeviation = "DesvioPadrao"
        case min = "Minimo"
        case max = "Maximo"
        case respondents = "numeroRespondentes"
        case baseCalculo = "baseCalculo"
    }

    var parsedDate: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: baseDate)
    }

    var formattedDate: String {
        guard let date = parsedDate else { return baseDate }
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.string(from: date)
    }

    var meetingNumber: Int? {
        // Format: "R8/2027" -> 8
        guard let range = meeting.range(of: "R") else { return nil }
        let afterR = meeting[range.upperBound...]
        guard let slashIndex = afterR.firstIndex(of: "/") else { return nil }
        return Int(afterR[..<slashIndex])
    }

    var meetingYear: Int? {
        // Format: "R8/2027" -> 2027
        guard let slashIndex = meeting.firstIndex(of: "/") else { return nil }
        return Int(meeting[meeting.index(after: slashIndex)...])
    }

    var formattedMeeting: String {
        // "R8/2027" -> "Reunião 8/2027"
        meeting.replacingOccurrences(of: "R", with: "Reunião ")
    }
}

// Inflation Expectations (IPCA 12 months)
struct InflationExpectationResponse: Codable {
    let value: [InflationExpectation]
}

struct InflationExpectation: Codable, Identifiable {
    var id: String { "\(indicator)-\(baseDate)-\(smoothed)-\(baseCalculo ?? 0)" }
    let indicator: String
    let baseDate: String
    let smoothed: String
    let mean: Double
    let median: Double
    let standardDeviation: Double?
    let min: Double
    let max: Double
    let respondents: Int?
    let baseCalculo: Int?

    enum CodingKeys: String, CodingKey {
        case indicator = "Indicador"
        case baseDate = "Data"
        case smoothed = "Suavizada"
        case mean = "Media"
        case median = "Mediana"
        case standardDeviation = "DesvioPadrao"
        case min = "Minimo"
        case max = "Maximo"
        case respondents = "numeroRespondentes"
        case baseCalculo = "baseCalculo"
    }

    var isSmoothed: Bool { smoothed == "S" }

    var parsedDate: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: baseDate)
    }

    var formattedDate: String {
        guard let date = parsedDate else { return baseDate }
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.string(from: date)
    }
}

// Annual Expectations (IPCA, PIB, Câmbio)
struct AnnualExpectationResponse: Codable {
    let value: [AnnualExpectation]
}

struct AnnualExpectation: Codable, Identifiable {
    var id: String { "\(indicator)-\(referenceYear)-\(baseDate)-\(baseCalculo ?? 0)" }
    let indicator: String
    let indicatorDetail: String?
    let baseDate: String
    let referenceYear: String
    let mean: Double
    let median: Double
    let standardDeviation: Double?
    let min: Double
    let max: Double
    let respondents: Int?
    let baseCalculo: Int?

    enum CodingKeys: String, CodingKey {
        case indicator = "Indicador"
        case indicatorDetail = "IndicadorDetalhe"
        case baseDate = "Data"
        case referenceYear = "DataReferencia"
        case mean = "Media"
        case median = "Mediana"
        case standardDeviation = "DesvioPadrao"
        case min = "Minimo"
        case max = "Maximo"
        case respondents = "numeroRespondentes"
        case baseCalculo = "baseCalculo"
    }

    var parsedDate: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: baseDate)
    }

    var formattedDate: String {
        guard let date = parsedDate else { return baseDate }
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.string(from: date)
    }
}

// Aggregated Focus Stats
struct FocusStats {
    var selicExpectations: [SelicExpectation] = []
    var ipcaExpectations: [InflationExpectation] = []
    var igpmExpectations: [InflationExpectation] = []
    var annualIPCA: [AnnualExpectation] = []
    var annualIGPM: [AnnualExpectation] = []
    var annualPIB: [AnnualExpectation] = []
    var annualCambio: [AnnualExpectation] = []

    // Latest SELIC expectation (Top 5 institutions, next meeting)
    var latestSelic: SelicExpectation? {
        // Get baseCalculo=1 (Top 5) for the nearest meeting
        selicExpectations
            .filter { $0.baseCalculo == 1 }
            .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) }
            .first
    }

    // Latest IPCA 12m expectation (Top 5)
    var latestIPCA12m: InflationExpectation? {
        ipcaExpectations
            .filter { $0.baseCalculo == 1 && $0.indicator == "IPCA" && !$0.isSmoothed }
            .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) }
            .first
    }

    // Latest IGP-M 12m expectation (Top 5)
    var latestIGPM12m: InflationExpectation? {
        igpmExpectations
            .filter { $0.baseCalculo == 1 && $0.indicator == "IGP-M" && !$0.isSmoothed }
            .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) }
            .first
    }

    // Latest annual IPCA for current year
    var latestIPCAYear: AnnualExpectation? {
        let currentYear = Calendar.current.component(.year, from: Date())
        return annualIPCA
            .filter { $0.baseCalculo == 0 && $0.referenceYear == "\(currentYear)" }
            .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) }
            .first
    }

    // Latest annual IGP-M for current year
    var latestIGPMYear: AnnualExpectation? {
        let currentYear = Calendar.current.component(.year, from: Date())
        return annualIGPM
            .filter { $0.baseCalculo == 0 && $0.referenceYear == "\(currentYear)" }
            .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) }
            .first
    }

    // Latest PIB for current year
    var latestPIBYear: AnnualExpectation? {
        let currentYear = Calendar.current.component(.year, from: Date())
        return annualPIB
            .filter { $0.baseCalculo == 0 && $0.referenceYear == "\(currentYear)" }
            .sorted { ($0.parsedDate ?? .distantPast) > ($1.parsedDate ?? .distantPast) }
            .first
    }

    // SELIC expectations grouped by meeting
    var selicByMeeting: [String: SelicExpectation] {
        var result: [String: SelicExpectation] = [:]
        // Get latest date for each meeting (baseCalculo=0 for all institutions)
        for exp in selicExpectations.filter({ $0.baseCalculo == 0 }) {
            if result[exp.meeting] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.meeting]?.parsedDate ?? .distantPast) {
                result[exp.meeting] = exp
            }
        }
        return result
    }

    // IPCA annual expectations by year
    var ipcaByYear: [String: AnnualExpectation] {
        var result: [String: AnnualExpectation] = [:]
        for exp in annualIPCA.filter({ $0.baseCalculo == 0 }) {
            if result[exp.referenceYear] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.referenceYear]?.parsedDate ?? .distantPast) {
                result[exp.referenceYear] = exp
            }
        }
        return result
    }

    // IGP-M annual expectations by year
    var igpmByYear: [String: AnnualExpectation] {
        var result: [String: AnnualExpectation] = [:]
        for exp in annualIGPM.filter({ $0.baseCalculo == 0 }) {
            if result[exp.referenceYear] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.referenceYear]?.parsedDate ?? .distantPast) {
                result[exp.referenceYear] = exp
            }
        }
        return result
    }

    // PIB annual expectations by year
    var pibByYear: [String: AnnualExpectation] {
        var result: [String: AnnualExpectation] = [:]
        for exp in annualPIB.filter({ $0.baseCalculo == 0 }) {
            if result[exp.referenceYear] == nil || (exp.parsedDate ?? .distantPast) > (result[exp.referenceYear]?.parsedDate ?? .distantPast) {
                result[exp.referenceYear] = exp
            }
        }
        return result
    }
}

// Legacy compatibility
struct ExpectationsResponse: Codable {
    let value: [MarketExpectation]
}

struct MarketExpectation: Codable, Identifiable {
    var id: String { "\(indicator)-\(referenceDate)-\(baseDate)" }
    let indicator: String
    let referenceDate: String
    let baseDate: String
    let mean: Double
    let median: Double
    let min: Double
    let max: Double
    let standardDeviation: Double?
    let respondents: Int?

    enum CodingKeys: String, CodingKey {
        case indicator = "Indicador"
        case referenceDate = "DataReferencia"
        case baseDate = "Data"
        case mean = "Media"
        case median = "Mediana"
        case min = "Minimo"
        case max = "Maximo"
        case standardDeviation = "DesvioPadrao"
        case respondents = "numeroRespondentes"
    }
}

// MARK: - Statistics Summary

struct InstitutionStats {
    var totalBanks: Int = 0
    var totalCooperatives: Int = 0
    var totalConsortia: Int = 0
    var totalSociedades: Int = 0

    var total: Int {
        totalBanks + totalCooperatives + totalConsortia + totalSociedades
    }

    var stateDistribution: [String: Int] = [:]
}


Summary: iOS app optimization: duplicates fixed, Apple auth added, block editor redesigned


## Análise do Projeto BCBDashboard-macOS

### Visão Geral
O **BCBDashboard** é um aplicativo nativo macOS desenvolvido em Swift/SwiftUI que consome as APIs públicas do Banco Central do Brasil (BCB). É um dashboard financeiro completo com foco em dados econômicos brasileiros.

### Arquitetura

| Camada | Padrão | Descrição |
|--------|--------|-----------|
| **App** | `@main` SwiftUI App | Entry point com múltiplas cenas (Window, Settings, MenuBar) |
| **Views** | SwiftUI + Swift Charts | Interface declarativa com gráficos nativos |
| **ViewModels** | ObservableObject | Estado reativo com `@Published` |
| **Services** | Singleton async/await | Cliente HTTP + cache + notificações |
| **Models** | Codable structs | Modelos de dados das APIs BCB |

### Estrutura de Arquivos (17 arquivos Swift)

```
BCBDashboard-macOS/
├── BCBDashboardApp.swift        # App principal + MenuBar + Settings (596 linhas)
├── Models/
│   ├── Currency.swift           # PTAX: DollarQuote, CurrencyQuote
│   ├── TimeSeries.swift         # SGS: SeriesCode (SELIC, IPCA, CDI, PIB)
│   └── Institution.swift        # Bank, Cooperative, Consortium, PIX, Focus (~1000 linhas)
├── Services/
│   ├── BCBAPIClient.swift       # Cliente HTTP unificado (549 linhas)
│   ├── NotificationService.swift
│   └── LogManager.swift
├── ViewModels/
│   └── DashboardViewModel.swift # Estado global (388 linhas)
├── Views/
│   ├── ContentView.swift        # NavigationSplitView + Sidebar
│   ├── Dashboard/DashboardView.swift  # Cards + Charts
│   ├── Currency/CurrencyListView.swift
│   ├── Rates/RatesView.swift
│   ├── Institutions/InstitutionsView.swift
│   ├── PIX/PIXStatsView.swift
│   └── Expectations/ExpectationsView.swift
└── Extensions/Extensions.swift   # Color, Double, Date helpers
```

### APIs Integradas

| API | Base URL | Dados |
|-----|----------|-------|
| **PTAX** | `olinda.bcb.gov.br/PTAX/v1` | Câmbio (USD, EUR, etc) |
| **SGS** | `api.bcb.gov.br/dados/serie` | Séries temporais (SELIC, IPCA, CDI) |
| **SPI** | `olinda.bcb.gov.br/SPI/v1` | Estatísticas PIX |
| **Expectativas** | `olinda.bcb.gov.br/Expectativas/v1` | Focus (projeções de mercado) |
| **Instituições** | `olinda.bcb.gov.br/Instituicoes_em_funcionamento/v1` | Bancos, cooperativas, fintechs |

### Features Implementadas

1. **Dashboard Principal**
   - Cards com cotações USD/EUR em tempo real
   - Taxas SELIC, IPCA, CDI com histórico
   - Gráficos (Swift Charts) de 30 dias
   - Juro real calculado (SELIC - IPCA)

2. **Menu Bar Widget**
   - Exibe USD e SELIC na barra de menus
   - Quick actions (refresh, abrir app, sair)

3. **Expectativas Focus**
   - SELIC esperada por reunião COPOM
   - IPCA/IGP-M 12 meses
   - PIB e Câmbio anuais

4. **Instituições Financeiras**
   - 4 categorias: Bancos, Cooperativas, Consórcios, Sociedades
   - Busca por nome/CNPJ
   - Distribuição por estado

5. **PIX Stats**
   - Volume diário/intraday
   - Disponibilidade SPI
   - Remuneração de contas

6. **Configurações Avançadas**
   - Auto-refresh configurável (30s a 1h)
   - Cache com expiração
   - Notificações de variação cambial
   - Exportação JSON/CSV
   - Launch at login (SMAppService)

### Padrões de Código

- **async/await** para todas as chamadas de rede
- **@MainActor** nos ViewModels e APIClient
- **LogManager** centralizado com categorias
- **CacheManager** com expiração configurável
- **Graceful degradation** para endpoints instáveis do BCB

### Requisitos

- **macOS 14.0+** (Sonoma)
- **Swift 5.9**
- **Xcode 15+**

### Pontos de Atenção

1. O projeto tem **Package.swift** mas usa **Xcode project** (`.xcodeproj`)
2. Alguns endpoints do BCB (PIX Availability, Remuneration) retornam 500 frequentemente
3. O modelo `Institution.swift` é muito grande (~1000 linhas) - poderia ser dividido