Files
Investidor-V2/resources/js/components/proactive-investment.tsx
T

1071 lines
43 KiB
TypeScript

import { createOperationKey } from '@/lib/operation-key';
import { Field, control, button } from '@/pages/management/shared';
import { ProactiveSupplyBrowser } from './proactive-supply';
export type ProactiveEvidence = {
id: string;
observation_id?: string;
category: string;
purpose: 'local' | 'methodology';
statement: string;
interpretation: string;
source: string;
locator: string;
period: string;
scope: string;
coverage: string;
verified_on: string;
access: 'internal' | 'public';
document_id: string;
};
export type ProactiveAnalysis = {
version: 1;
kind: 'exists' | 'needs' | 'ideas';
evidence: ProactiveEvidence[];
need: {
state: 'hypothesis' | 'confirmed';
problem: string;
observed_supply: string;
observed_demand: string;
type: string;
clients: string;
evidence_ids: string[];
limitations: string;
missing: string;
confirmation: string;
};
idea: {
solution: string;
activity: string;
chain_id: string;
stage: string;
coverage: string;
mode: string;
resources: string;
assumptions: string;
conditions: string;
pending: string;
next_action: string;
};
relations: {
id: string;
kind: string;
target_id: string;
relation: string;
reference: string;
evidence_ids: string[];
}[];
supply_comparisons?: {
observation_id: string;
relationship:
| 'competition'
| 'complementarity'
| 'partner'
| 'uncertain';
rationale: string;
limitations: string;
}[];
};
export type ProactiveOptions = {
documents?: { id: string; title: string }[];
groups: Record<string, string>;
references: Record<string, { id: string; name: string }[]>;
};
export type ProactivePublic = {
version?: number;
group: string;
state: string;
need: string;
proposal: string;
basis: string;
limitations: string;
next_action: string;
evidence_ids: string[];
chain_id?: string | null;
stage?: string | null;
notice?: string;
resources?: string;
assumptions?: string;
conditions?: string;
relation_ids?: string[];
relations?: {
kind: string;
target_id: string;
label: string;
relationship: string;
basis: string;
}[];
evidence?: Partial<ProactiveEvidence>[];
internal_comparisons?: {
relationship: string;
rationale: string;
limitations: string;
statement?: string;
source?: string;
locator?: string;
period?: string;
}[];
};
export const proactiveNotice =
'Ausência de registo não comprova ausência de oferta. Proximidade não comprova contrato, participação, energia acessível, terra disponível ou benefício fiscal. Necessidade confirmada não comprova viabilidade comercial; hipótese de fornecimento não é encomenda.';
export const newProactive = (): ProactiveAnalysis => ({
version: 1,
kind: 'ideas',
evidence: [],
need: {
state: 'hypothesis',
problem: '',
observed_supply: '',
observed_demand: '',
type: '',
clients: '',
evidence_ids: [],
limitations: '',
missing: '',
confirmation: '',
},
idea: {
solution: '',
activity: '',
chain_id: '',
stage: '',
coverage: '',
mode: '',
resources: '',
assumptions: '',
conditions: '',
pending: '',
next_action: '',
},
relations: [],
});
export const newPublicProactive = (): ProactivePublic => ({
group: 'ideas',
state: 'hypothesis',
need: '',
proposal: '',
basis: '',
limitations: '',
next_action: '',
evidence_ids: [],
chain_id: '',
stage: '',
resources: '',
assumptions: '',
conditions: '',
relation_ids: [],
});
export function ProactiveInvestmentEditor({
value,
onChange,
options,
history = [],
territoryId,
recordId,
}: {
value?: ProactiveAnalysis;
onChange: (value: ProactiveAnalysis) => void;
options?: ProactiveOptions;
history?: {
version: number;
at: string;
state: string;
problem: string;
confirmation: string;
}[];
territoryId?: string;
recordId?: number;
}) {
if (!value)
return (
<section className="rounded border p-4">
<h2 className="font-semibold">
Análise territorial e preparação de ideias
</h2>
<p>
Comece pelo território e sector existentes, mesmo sem
oportunidade publicada, promotor, montante ou coordenadas. O
rascunho e a publicação são independentes.
</p>
<button
type="button"
className={button}
onClick={() => onChange(newProactive())}
>
Preparar análise e ideia
</button>
</section>
);
const patch = (p: Partial<ProactiveAnalysis>) =>
onChange({ ...value, ...p });
const removeEvidence = (id: string) => {
const proof = value.evidence.find((e) => e.id === id);
patch({
evidence: value.evidence.filter((e) => e.id !== id),
need: {
...value.need,
evidence_ids: value.need.evidence_ids.filter((x) => x !== id),
},
relations: value.relations.map((r) => ({
...r,
evidence_ids: r.evidence_ids.filter((x) => x !== id),
})),
...(proof?.observation_id
? {
supply_comparisons: (
value.supply_comparisons ?? []
).filter(
(c) => c.observation_id !== proof.observation_id,
),
}
: {}),
});
};
const text = (label: string, v: string, change: (v: string) => void) => (
<Field label={label}>
<textarea
className={control}
aria-label={label}
value={v ?? ''}
onChange={(e) => change(e.target.value)}
/>
</Field>
);
const select = (
label: string,
v: string,
choices: Record<string, string>,
change: (v: string) => void,
) => (
<Field label={label}>
<select
className={control}
aria-label={label}
value={v ?? ''}
onChange={(e) => change(e.target.value)}
>
<option value="">Por determinar</option>
{Object.entries(choices).map(([key, label]) => (
<option key={key} value={key}>
{label}
</option>
))}
</select>
</Field>
);
const evidenceChoice = (
selected: string[],
change: (v: string[]) => void,
) => (
<fieldset>
<legend>Evidências que fundamentam esta entrada</legend>
{value.evidence.map((e, i) => (
<label key={e.id} className="block">
<input
type="checkbox"
checked={selected.includes(e.id)}
onChange={(event) =>
change(
event.target.checked
? [...selected, e.id]
: selected.filter((id) => id !== e.id),
)
}
/>{' '}
Evidência {i + 1}: {e.statement || 'Por descrever'} (
{e.access === 'public' ? 'Pública autorizada' : 'Interna'})
</label>
))}
</fieldset>
);
return (
<section
className="space-y-4 rounded border p-4"
aria-label="Análise territorial e ideia"
>
<h2 className="text-xl font-semibold">
Território e sector → evidências → necessidade → ideia
</h2>
<details>
<summary>Como fundamentar esta análise</summary>
<p>
Inventarie actividades, recursos, infra-estruturas e
serviços de apoio. Confronte oferta observada com procura
documentada e identifique os clientes ou beneficiários.
Registe o período, cobertura e limitações de cada
observação. Use referências internacionais para orientar as
perguntas; a confirmação do caso depende de evidência local.
</p>
</details>
<p>
Reutiliza o sector e a localização principal escolhidos nesta
ficha. Guarde pelo botão de rascunho existente. Nenhum montante,
promotor ou ponto exacto é exigido.
</p>
<p>{proactiveNotice}</p>
{select(
'Tipo de conteúdo em preparação',
value.kind,
{
exists: 'O que existe',
needs: 'Necessidade identificada',
ideas: 'Ideia de investimento',
},
(v) => patch({ kind: v as ProactiveAnalysis['kind'] }),
)}
<ProactiveSupplyBrowser
value={value}
onChange={onChange}
territoryId={territoryId}
recordId={recordId}
/>
<fieldset className="space-y-3 rounded border p-3">
<legend>Evidências e proveniência</legend>
<p>
Registe factos e interpretações em campos separados. Listas
de empresas, hotéis, restaurantes, contactos e documentos
internos permanecem internos; não são copiados para a
divulgação.
</p>
{value.evidence.map((e, i) => {
const change = (p: Partial<ProactiveEvidence>) =>
patch({
evidence: value.evidence.map((x) =>
x.id === e.id ? { ...x, ...p } : x,
),
});
if (e.observation_id)
return (
<fieldset
key={e.id}
className="space-y-2 rounded border p-3"
>
<legend>
Evidência interna da oferta {i + 1}
</legend>
<p>{e.statement}</p>
<p>
Fonte: {e.source} · {e.locator}
</p>
<p>
Período: {e.period} · Abrangência: {e.scope}
</p>
<p>{e.coverage}</p>
<p>
A proveniência é recuperada do original. A
data indica consulta documental, sem
confirmação de actividade actual.
</p>
{text(
`Interpretação ou hipótese ${i + 1}`,
e.interpretation,
(v) => change({ interpretation: v }),
)}
<button
type="button"
onClick={() => removeEvidence(e.id)}
>
Retirar evidência {i + 1} e a sua comparação
deste rascunho
</button>
</fieldset>
);
return (
<fieldset
key={e.id}
className="space-y-2 rounded border p-3"
>
<legend>Evidência {i + 1}</legend>
{select(
`Finalidade da evidência ${i + 1}`,
e.purpose ?? '',
{
local: 'Evidência local do caso',
methodology:
'Referência metodológica — não prova procura local',
},
(v) =>
change({
purpose: v as 'local' | 'methodology',
}),
)}
{select(
`Categoria da evidência ${i + 1}`,
e.category,
{
activity: 'Actividade',
resource: 'Recurso',
infrastructure: 'Infra-estrutura',
supply: 'Oferta',
demand: 'Procura',
workforce:
'Mão-de-obra — evidência documentada',
},
(v) => change({ category: v }),
)}
{(
[
['statement', 'Facto documentado'],
[
'interpretation',
'Interpretação ou hipótese',
],
['source', 'Fonte e exemplar'],
['locator', 'Localizador ou trecho'],
['period', 'Período da evidência'],
['scope', 'Abrangência'],
['coverage', 'Cobertura e limitações'],
] as const
).map(([key, label]) => (
<div key={key}>
{text(`${label} ${i + 1}`, e[key], (v) =>
change({ [key]: v }),
)}
</div>
))}
<Field label={`Verificação ${i + 1}`}>
<input
aria-label={`Verificação ${i + 1}`}
className={control}
type="date"
value={e.verified_on ?? ''}
onChange={(event) =>
change({
verified_on: event.target.value,
})
}
/>
</Field>
<Field
label={`Documento existente associado ${i + 1}`}
>
<select
aria-label={`Documento existente associado ${i + 1}`}
className={control}
value={e.document_id ?? ''}
onChange={(event) =>
change({
document_id: event.target.value,
})
}
>
<option value="">
Sem documento associado — indicar fonte
e localizador
</option>
{options?.documents?.map((d) => (
<option key={d.id} value={d.id}>
{d.title}
</option>
))}
</select>
</Field>
{select(
`Divulgação da evidência ${i + 1}`,
e.access,
{
internal: 'Interna — análise privada',
public: 'Pública — origem e divulgação autorizadas',
},
(v) =>
change({
access: v as ProactiveEvidence['access'],
}),
)}
<button
type="button"
className="underline"
onClick={() =>
patch({
evidence: value.evidence.filter(
(x) => x.id !== e.id,
),
need: {
...value.need,
evidence_ids:
value.need.evidence_ids.filter(
(id) => id !== e.id,
),
},
relations: value.relations.map((r) => ({
...r,
evidence_ids: r.evidence_ids.filter(
(id) => id !== e.id,
),
})),
})
}
>
Retirar evidência {i + 1} deste rascunho
</button>
</fieldset>
);
})}
<button
type="button"
className={button}
onClick={() =>
patch({
evidence: [
...value.evidence,
{
id: createOperationKey(),
category: 'activity',
purpose: 'local',
statement: '',
interpretation: '',
source: '',
locator: '',
period: '',
scope: '',
coverage: '',
verified_on: '',
access: 'internal',
document_id: '',
},
],
})
}
>
Acrescentar evidência
</button>
</fieldset>
{value.kind !== 'exists' && (
<fieldset className="space-y-3 rounded border p-3">
<legend>Necessidade ou lacuna</legend>
{select(
'Estado da necessidade',
value.need.state,
{
hypothesis: 'Hipótese analítica',
confirmed:
'Necessidade confirmada — fundamento documentado',
},
(v) =>
patch({
need: {
...value.need,
state: v as 'hypothesis' | 'confirmed',
},
}),
)}
{select(
'Tipo de lacuna',
value.need.type,
{
quantity: 'Quantidade',
quality: 'Qualidade',
logistics: 'Logística',
processing: 'Transformação',
coordination: 'Coordenação',
support: 'Serviços de apoio',
},
(v) => patch({ need: { ...value.need, type: v } }),
)}
{(
[
['problem', 'Problema identificado'],
['observed_supply', 'Oferta observada'],
['observed_demand', 'Procura observada'],
['clients', 'Potenciais clientes ou beneficiários'],
['limitations', 'Limitações da necessidade'],
['missing', 'Informação por confirmar'],
[
'confirmation',
'Fundamentação do estado da necessidade',
],
] as const
).map(([key, label]) => (
<div key={key}>
{text(label, value.need[key], (v) =>
patch({ need: { ...value.need, [key]: v } }),
)}
</div>
))}
{evidenceChoice(value.need.evidence_ids, (v) =>
patch({ need: { ...value.need, evidence_ids: v } }),
)}
</fieldset>
)}
{value.kind === 'ideas' && (
<fieldset className="space-y-3 rounded border p-3">
<legend>Ideia em preparação</legend>
{(
[
['solution', 'Solução proposta'],
['activity', 'Actividade proposta'],
['stage', 'Etapa da cadeia de valor'],
['coverage', 'Localização ou abrangência da ideia'],
['resources', 'Recursos documentados'],
['assumptions', 'Pressupostos explícitos'],
['conditions', 'Condições da ideia'],
['pending', 'Pendências da ideia'],
['next_action', 'Próxima acção'],
] as const
).map(([key, label]) => (
<div key={key}>
{text(label, value.idea[key], (v) =>
patch({ idea: { ...value.idea, [key]: v } }),
)}
</div>
))}
{select(
'Cadeia de valor existente',
value.idea.chain_id,
Object.fromEntries(
(options?.references.chain ?? []).map((c) => [
c.id,
c.name,
]),
),
(v) => patch({ idea: { ...value.idea, chain_id: v } }),
)}
{select(
'Possibilidade de investimento',
value.idea.mode,
{
create: 'Criar actividade',
expand: 'Expandir actividade',
associate: 'Associar actividades existentes',
},
(v) => patch({ idea: { ...value.idea, mode: v } }),
)}
</fieldset>
)}
{value.kind !== 'ideas' && (
<p>
Os campos anteriores da ideia são conservados. Escolha
“Ideia de investimento” quando pretender preparar uma
solução.
</p>
)}
<fieldset className="space-y-3 rounded border p-3">
<legend>Relações com estruturas existentes</legend>
{value.relations.map((r, i) => {
const change = (p: Partial<typeof r>) =>
patch({
relations: value.relations.map((x) =>
x.id === r.id ? { ...x, ...p } : x,
),
});
return (
<fieldset key={r.id} className="rounded border p-3">
<legend>Relação {i + 1}</legend>
{select(
`Tipo de referência ${i + 1}`,
r.kind,
{
project: 'Projecto âncora',
cluster: 'Cluster',
corridor: 'Corredor',
park: 'Parque',
chain: 'Cadeia de valor',
},
(v) => change({ kind: v, target_id: '' }),
)}
{select(
`Referência existente ${i + 1}`,
r.target_id,
Object.fromEntries(
(options?.references[r.kind] ?? []).map(
(c) => [c.id, c.name],
),
),
(v) => change({ target_id: v }),
)}
{select(
`Fundamento da relação ${i + 1}`,
r.relation,
{
documented: 'Documentada',
hypothesis: 'Hipótese de relação',
proximity: 'Proximidade, sem compromisso',
},
(v) => change({ relation: v }),
)}
{text(
`Fonte da relação ${i + 1}`,
r.reference,
(v) => change({ reference: v }),
)}
{evidenceChoice(r.evidence_ids, (v) =>
change({ evidence_ids: v }),
)}
<button
type="button"
className="underline"
onClick={() =>
patch({
relations: value.relations.filter(
(x) => x.id !== r.id,
),
})
}
>
Retirar relação {i + 1}
</button>
</fieldset>
);
})}
<button
type="button"
className={button}
onClick={() =>
patch({
relations: [
...value.relations,
{
id: createOperationKey(),
kind: 'project',
target_id: '',
relation: 'hypothesis',
reference: '',
evidence_ids: [],
},
],
})
}
>
Relacionar referência existente
</button>
</fieldset>
{!!history.length && (
<details>
<summary>Histórico do fundamento da necessidade</summary>
{history.map((h) => (
<p key={h.version}>
Versão {h.version} · {h.at} ·{' '}
{h.state === 'confirmed'
? 'Necessidade confirmada'
: 'Hipótese'}
: {h.problem} —{' '}
{h.confirmation || 'Fundamentação por completar'}
</p>
))}
</details>
)}
<p>
Não calcula mercado, investimento ou rentabilidade. Informação
em falta permanece por determinar. Uma nova edição não altera os
históricos nem publica a análise.
</p>
</section>
);
}
export function ProactiveSummary({
value,
}: {
value?: ProactivePublic | null;
}) {
if (!value) return null;
return (
<section
className="space-y-2 rounded border p-4"
aria-label="Origem proactiva do investimento"
>
<h2 className="text-xl font-semibold">
{value.group === 'ideas'
? 'Ideia em preparação'
: value.group === 'needs'
? 'Necessidade identificada'
: 'O que existe'}
</h2>
<p>
{value.group === 'exists'
? 'Registo documental; consultar abrangência, cobertura e limitações das fontes.'
: value.state === 'confirmed'
? 'Necessidade confirmada no âmbito documentado'
: 'Hipótese analítica; conclusão económica por confirmar'}
</p>
{(
[
['need', 'Necessidade'],
['proposal', 'Proposta'],
['basis', 'Fundamento'],
['limitations', 'Limitações'],
['next_action', 'Próximo passo'],
['stage', 'Etapa da cadeia'],
['resources', 'Recursos'],
['assumptions', 'Pressupostos'],
['conditions', 'Condições'],
] as const
).map(([key, label]) =>
value[key] ? (
<p key={key}>
<strong>{label}:</strong> {value[key]}
</p>
) : null,
)}
{value.internal_comparisons?.map((c, i) => (
<div key={i} className="rounded border p-3">
<strong>
Comparação interna da oferta {i + 1} ·{' '}
{
(
{
competition: 'Possível concorrência',
complementarity:
'Possível complementaridade',
partner:
'Possível parceiro, sem compromisso',
uncertain: 'Relação por determinar',
} as Record<string, string>
)[c.relationship]
}
</strong>
<p>{c.statement}</p>
<p>
Fonte: {c.source} · {c.locator} · {c.period}
</p>
<p>{c.rationale}</p>
<p>Incertezas: {c.limitations}</p>
</div>
))}
{value.relations?.map((r) => (
<p key={r.kind + r.target_id}>
Relação seleccionada: {r.label} ·{' '}
{r.relationship === 'documented'
? 'Documentada'
: r.relationship === 'proximity'
? 'Proximidade, sem pertença'
: 'Hipótese de relação'}
. Fundamento: {r.basis}. Não confirma contrato, acesso ou
participação.
</p>
))}
{value.evidence?.map((e, i) => (
<p key={e.id ?? i}>
Evidência pública: {e.statement}; fonte: {e.source};
localizador: {e.locator}; período: {e.period}; abrangência:{' '}
{e.scope}; cobertura: {e.coverage}; verificação:{' '}
{e.verified_on}.
</p>
))}
<p>{value.notice ?? proactiveNotice}</p>
<p>
Sem disponibilidade de participação, viabilidade comercial ou
benefício fiscal presumidos.
</p>
</section>
);
}
export function ProactivePublicationFields({
value,
onChange,
evidence,
relations = [],
options,
reviewed,
onReview,
}: {
value: ProactivePublic;
onChange: (v: ProactivePublic) => void;
evidence: ProactiveEvidence[];
relations?: { id: string; label: string; relationship: string }[];
options?: ProactiveOptions;
reviewed: boolean;
onReview: (v: boolean) => void;
}) {
const text = (
key:
| 'need'
| 'proposal'
| 'basis'
| 'limitations'
| 'next_action'
| 'stage'
| 'resources'
| 'assumptions'
| 'conditions',
label: string,
) => (
<Field label={label}>
<textarea
aria-label={label}
className={control}
value={value[key] ?? ''}
onChange={(e) => {
onChange({ ...value, [key]: e.target.value });
onReview(false);
}}
/>
</Field>
);
return (
<fieldset className="space-y-3 rounded border p-4">
<legend>Síntese proactiva expressamente pública</legend>
<p>
Redija uma síntese independente. Nenhum facto, interpretação,
contacto ou fonte interna é copiado automaticamente. A síntese
também não pode revelar informação privada indirectamente.
</p>
<Field label="Grupo público">
<select
className={control}
aria-label="Grupo público"
value={value.group}
onChange={(e) =>
onChange({ ...value, group: e.target.value })
}
>
{Object.entries(
options?.groups ?? {
exists: 'O que existe',
needs: 'Necessidades identificadas',
ideas: 'Ideias de investimento',
},
).map(([k, v]) => (
<option key={k} value={k}>
{v}
</option>
))}
</select>
</Field>
{value.group !== 'exists' && (
<Field label="Estado público da necessidade">
<select
className={control}
aria-label="Estado público da necessidade"
value={value.state}
onChange={(e) =>
onChange({ ...value, state: e.target.value })
}
>
<option value="hypothesis">Hipótese analítica</option>
<option value="confirmed">
Necessidade confirmada com fundamento público
</option>
</select>
</Field>
)}
{value.group !== 'exists' && text('need', 'Necessidade pública')}
{value.group === 'ideas' && text('proposal', 'Proposta pública')}
{text('basis', 'Fundamento público')}
{text('limitations', 'Limitações públicas da ideia')}
{text('next_action', 'Próximo passo público')}
{text('stage', 'Etapa pública da cadeia')}
{text('resources', 'Recursos públicos documentados')}
{text('assumptions', 'Pressupostos públicos')}
{text('conditions', 'Condições públicas')}
<Field label="Cadeia de valor pública">
<select
className={control}
aria-label="Cadeia de valor pública"
value={value.chain_id ?? ''}
onChange={(e) =>
onChange({ ...value, chain_id: e.target.value })
}
>
<option value="">Por determinar</option>
{options?.references.chain?.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))}
</select>
</Field>
<fieldset>
<legend>Relações públicas seleccionadas</legend>
{relations.map((r) => (
<label key={r.id} className="block">
<input
type="checkbox"
checked={(value.relation_ids ?? []).includes(r.id)}
onChange={(event) =>
onChange({
...value,
relation_ids: event.target.checked
? [...(value.relation_ids ?? []), r.id]
: (value.relation_ids ?? []).filter(
(id) => id !== r.id,
),
})
}
/>{' '}
{r.label} · {r.relationship}
</label>
))}
</fieldset>
<fieldset>
<legend>Evidências públicas a divulgar</legend>
{evidence.map((e) => (
<label key={e.id} className="block">
<input
type="checkbox"
checked={value.evidence_ids.includes(e.id)}
onChange={(event) =>
onChange({
...value,
evidence_ids: event.target.checked
? [...value.evidence_ids, e.id]
: value.evidence_ids.filter(
(id) => id !== e.id,
),
})
}
/>{' '}
{e.statement || 'Evidência pública por descrever'}
</label>
))}
</fieldset>
<label className="block">
<input
type="checkbox"
checked={reviewed}
onChange={(e) => onReview(e.target.checked)}
/>{' '}
Confirmei a divulgação deste texto e das evidências públicas
seleccionadas, sem exposição directa ou indirecta de informação
privada.
</label>
</fieldset>
);
}
export function proactiveView(raw: unknown): ProactivePublic | null {
if (!raw || typeof raw !== 'object') return null;
const p = raw as Partial<ProactivePublic & ProactiveAnalysis>;
if (p.version !== 1) return null;
if (typeof p.group === 'string' && typeof p.need === 'string')
return p as ProactivePublic;
const a = raw as ProactiveAnalysis;
if (!a.need || typeof a.need.problem !== 'string' || !a.idea) return null;
return {
version: 1,
group: a.kind,
state: a.need.state,
need: a.kind === 'exists' ? '' : a.need.problem,
proposal: a.kind === 'ideas' ? a.idea.solution : '',
basis: a.need.confirmation || 'Fundamento por completar',
limitations: [
a.need.limitations,
a.need.missing,
a.idea.assumptions,
a.idea.conditions,
a.idea.pending,
]
.filter(Boolean)
.join('; '),
next_action: a.idea.next_action,
evidence_ids: a.need.evidence_ids,
chain_id: a.idea.chain_id,
stage: a.kind === 'ideas' ? a.idea.stage : '',
notice: 'Origem privada autorizada deste dossier. ' + proactiveNotice,
internal_comparisons: (a.supply_comparisons ?? []).map((c) => {
const e = a.evidence.find(
(e) => e.observation_id === c.observation_id,
);
return {
...c,
statement: e?.statement,
source: e?.source,
locator: e?.locator,
period: e?.period,
};
}),
};
}