172 lines
6.6 KiB
TypeScript
172 lines
6.6 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;
|
|
}
|
|
|
|
/** Only a documented point inside the requested territory establishes local presence. */
|
|
export function matchingInstitutionLocations<
|
|
T extends {
|
|
territory_id?: string;
|
|
territory_path?: { id?: string; name: string }[];
|
|
evidence_status?: string | null;
|
|
},
|
|
>(locations: T[], territory: string): T[] {
|
|
return locations.filter(
|
|
(location) =>
|
|
!["to_confirm", "historical", "conflict", "unlocated"].includes(
|
|
location.evidence_status ?? "reported",
|
|
) &&
|
|
(location.territory_id === territory ||
|
|
location.territory_path?.some((node) => node.id === territory)),
|
|
);
|
|
}
|