172 lines
6.2 KiB
TypeScript
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;
|
|
}
|