feat: add initial application structure with configuration, UI components, and state management

This commit is contained in:
Jennie Robinson Faber 2025-08-09 18:13:16 +01:00
parent fadf94002c
commit 0af6b17792
56 changed files with 6137 additions and 129 deletions

View file

@ -0,0 +1,59 @@
/**
* Returns Top source % and HHI-based traffic light (HHI hidden from UI)
* Uses Herfindahl-Hirschman Index internally but only exposes traffic light color
*/
export const useConcentration = () => {
const calculateTopSourcePct = (revenueShares: number[]): number => {
if (revenueShares.length === 0) return 0
return Math.max(...revenueShares)
}
const calculateHHI = (revenueShares: number[]): number => {
// HHI = sum of squared market shares (as percentages)
return revenueShares.reduce((sum, share) => sum + (share * share), 0)
}
const getConcentrationStatus = (topSourcePct: number, hhi: number): 'green' | 'yellow' | 'red' => {
// Primary threshold based on top source %
if (topSourcePct > 50) return 'red'
if (topSourcePct > 35) return 'yellow'
// Secondary check using HHI for more nuanced analysis
if (hhi > 2500) return 'red' // Highly concentrated
if (hhi > 1500) return 'yellow' // Moderately concentrated
return 'green'
}
const getConcentrationMessage = (status: 'green' | 'yellow' | 'red'): string => {
switch (status) {
case 'red':
return 'Most of your money comes from one place. Add another stream to reduce risk.'
case 'yellow':
return 'Revenue somewhat concentrated. Consider diversifying further.'
case 'green':
return 'Good revenue diversification.'
}
}
const analyzeConcentration = (revenueStreams: Array<{ targetPct: number }>) => {
const shares = revenueStreams.map(stream => stream.targetPct || 0)
const topSourcePct = calculateTopSourcePct(shares)
const hhi = calculateHHI(shares)
const status = getConcentrationStatus(topSourcePct, hhi)
return {
topSourcePct,
status,
message: getConcentrationMessage(status)
// Note: HHI is deliberately not exposed in return
}
}
return {
calculateTopSourcePct,
getConcentrationStatus,
getConcentrationMessage,
analyzeConcentration
}
}