108 lines
3.2 KiB
TypeScript
108 lines
3.2 KiB
TypeScript
export const forceLabels = {
|
|
rivalry: 'Rivalidade entre concorrentes',
|
|
entrants: 'Ameaça de novos entrantes',
|
|
substitutes: 'Ameaça de produtos ou serviços substitutos',
|
|
suppliers: 'Poder negocial dos fornecedores',
|
|
buyers: 'Poder negocial dos clientes',
|
|
} as const;
|
|
|
|
export type Force = keyof typeof forceLabels;
|
|
export type Assessment = {
|
|
pressure: '' | 'low' | 'medium' | 'high';
|
|
evidence: string;
|
|
source: string;
|
|
period: string;
|
|
rationale: string;
|
|
limitations: string;
|
|
opportunity: string;
|
|
};
|
|
export type CompetitivenessDraft = {
|
|
version: 1;
|
|
clusterId: string;
|
|
sector: string;
|
|
region: string;
|
|
forces: Record<Force, Assessment>;
|
|
};
|
|
|
|
export const forces = Object.keys(forceLabels) as Force[];
|
|
const fields = [
|
|
'evidence',
|
|
'source',
|
|
'period',
|
|
'rationale',
|
|
'limitations',
|
|
'opportunity',
|
|
] as const;
|
|
|
|
export function newCompetitivenessDraft(
|
|
clusterId: string,
|
|
sector: string,
|
|
): CompetitivenessDraft {
|
|
return {
|
|
version: 1,
|
|
clusterId,
|
|
sector,
|
|
region: '',
|
|
forces: Object.fromEntries(
|
|
forces.map((force) => [
|
|
force,
|
|
{
|
|
pressure: '',
|
|
evidence: '',
|
|
source: '',
|
|
period: '',
|
|
rationale: '',
|
|
limitations: '',
|
|
opportunity: '',
|
|
},
|
|
]),
|
|
) as Record<Force, Assessment>,
|
|
};
|
|
}
|
|
|
|
// A pressure is an author's qualitative assessment, never inferred from missing data.
|
|
export function assessmentReady(row: Assessment): boolean {
|
|
return (
|
|
['low', 'medium', 'high'].includes(row.pressure) &&
|
|
fields.every((field) => row[field].trim().length > 0)
|
|
);
|
|
}
|
|
|
|
export function diagnosticReady(draft: CompetitivenessDraft): boolean {
|
|
return (
|
|
Boolean(draft.sector.trim() && draft.region.trim()) &&
|
|
forces.every((force) => assessmentReady(draft.forces[force]))
|
|
);
|
|
}
|
|
|
|
export function readCompetitivenessDraft(
|
|
raw: string,
|
|
clusterId: string,
|
|
): CompetitivenessDraft {
|
|
if (raw.length > 250_000)
|
|
throw Error('O ficheiro excede o limite de 250 KB.');
|
|
const input = JSON.parse(raw);
|
|
if (!input || input.version !== 1 || input.clusterId !== clusterId) {
|
|
throw Error(
|
|
'O ficheiro não corresponde a este cluster ou tem uma versão incompatível.',
|
|
);
|
|
}
|
|
const text = (value: unknown, max: number): string => {
|
|
if (typeof value !== 'string' || value.length > max)
|
|
throw Error('O ficheiro contém campos inválidos.');
|
|
return value;
|
|
};
|
|
const draft = newCompetitivenessDraft(clusterId, text(input.sector, 200));
|
|
draft.region = text(input.region, 1000);
|
|
for (const force of forces) {
|
|
const row = input.forces?.[force];
|
|
if (!row || !['', 'low', 'medium', 'high'].includes(row.pressure)) {
|
|
throw Error('O ficheiro não contém as cinco forças válidas.');
|
|
}
|
|
draft.forces[force].pressure = row.pressure;
|
|
for (const field of fields)
|
|
draft.forces[force][field] = text(row[field], 5000);
|
|
}
|
|
return draft;
|
|
}
|