Files
Investidor-V2/resources/js/lib/funding-public-presentation.ts
T

172 lines
6.2 KiB
TypeScript

export type PublicFunding = {
title: string;
kind: string;
summary: string;
institution_types?: string[];
institution_services?: string[];
products?: {
title: string;
instrument?: string | null;
availability?: string;
contact_url?: string | null;
source_url?: string | null;
}[];
};
const descriptions: Record<string, string> = {
commercial_bank: 'Instituição de banca comercial.',
investment_bank: 'Instituição especializada em banca de investimento.',
bank: 'Instituição bancária.',
microbank: 'Instituição de microfinanças.',
cooperative: 'Cooperativa de crédito.',
savings: 'Organização de poupança e empréstimo.',
investment_manager:
'Sociedade especializada na gestão de fundos de investimento.',
investment_company: 'Sociedade de investimento.',
pension_manager: 'Entidade gestora de fundos de pensões.',
insurer: 'Instituição do sector segurador.',
reinsurer: 'Instituição especializada em resseguros.',
microinsurer: 'Instituição de microsseguros.',
broker: 'Entidade de corretagem de seguros.',
agent: 'Entidade de mediação de seguros.',
reinsurance_broker: 'Entidade de corretagem de resseguros.',
exchange_house: 'Instituição de serviços cambiais.',
electronic_money: 'Instituição de moeda electrónica.',
interbank: 'Entidade de serviços interbancários.',
supervisor: 'Entidade de supervisão do sistema financeiro.',
};
const services: Record<string, string> = {
equity: 'Capital e participação no investimento',
foreign_exchange: 'Câmbio',
guarantee: 'Garantias',
insurance: 'Seguros',
investment: 'Financiamento do investimento',
investment_management: 'Gestão de investimentos',
payments: 'Serviços de pagamento',
pension: 'Pensões',
working_capital: 'Financiamento de tesouraria',
};
export function fundingPublicPresentation(p: PublicFunding) {
const generic =
!p.summary.trim() ||
p.summary.trim() ===
'Instituição identificada no documento fornecido. Categoria, localização e contactos são informativos; produtos, disponibilidade e condições não documentados permanecem por confirmar.';
let description = generic
? (descriptions[p.institution_types?.[0] ?? ''] ??
'Entidade do sector financeiro.')
: p.summary
.replace(
/\s*Referência informativa, não concessão de financiamento\.?$/,
'',
)
.replace(/Público\/finalidade:/g, 'Soluções para')
.replace(/\.\./g, '.')
.trim();
const repeatedInstrument = (p.products ?? []).some(
(product) =>
product.instrument?.trim() &&
description.startsWith(product.instrument.trim()),
);
if (repeatedInstrument) {
const audience = description.match(
/Soluções para\s+(.+?)(?:\.|$)/i,
)?.[1];
description = audience
? 'Soluções financeiras para ' +
audience.charAt(0).toLowerCase() +
audience.slice(1) +
'.'
: (descriptions[p.institution_types?.[0] ?? ''] ?? description);
}
const products = (p.products ?? [])
.filter((x) => x.availability !== 'closed')
.map((x) => ({
title: x.title.startsWith(p.title + ' — ')
? x.title.slice(p.title.length + 3)
: x.title,
detail: x.instrument?.trim() ?? '',
url: x.contact_url || x.source_url || null,
}));
return {
description,
products,
services: [
...new Set(
(p.institution_services ?? [])
.map((s) => services[s])
.filter(Boolean),
),
],
};
}
const normal = (s: string) =>
s
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, ' ')
.trim();
export function publicInstitutionContacts<
T extends { kind: string; value: string },
>(rows: T[]) {
const seen = new Set<string>();
return rows.flatMap((row) => {
const value =
row.kind === 'phone'
? row.value.replace(/\s*Não encontrado\.?$/i, '').trim()
: row.value.trim();
if (!value) return [];
const key =
row.kind +
':' +
(row.kind === 'phone'
? value.replace(/\D/g, '')
: value.toLowerCase().replace(/\/$/, ''));
if (seen.has(key)) return [];
seen.add(key);
return [{ ...row, value }];
});
}
export function publicInstitutionLocations<
T extends {
role: string;
address?: string | null;
location_text?: string;
territory_path?: { name: string }[];
},
>(rows: T[]) {
const result: T[] = [];
for (const row of [...rows].sort(
(a, b) =>
(a.role === 'headquarters' ? 0 : 1) -
(b.role === 'headquarters' ? 0 : 1),
)) {
if (!row.address?.trim()) continue;
const address = normal(row.address);
const duplicate = result.some((previous) => {
const other = normal(previous.address ?? '');
if (address === other) return true;
const short = address.length < other.length ? address : other,
long = address.length < other.length ? other : address;
if (!long.startsWith(short + ' ')) return false;
const rest = long.slice(short.length).trim();
const localities = [
row.location_text ?? '',
previous.location_text ?? '',
...(row.territory_path ?? []).map((x) => x.name),
...(previous.territory_path ?? []).map((x) => x.name),
].map(normal);
return (
localities.includes(rest) ||
localities.some(
(x) =>
x.replace(/^cidade de /, '') ===
rest.replace(/^cidade de /, ''),
)
);
});
if (!duplicate) result.push(row);
}
return result;
}