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

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)),
);
}