Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions src/services/country-instability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ function calcConflictScore(data: CountryData, countryCode: string): number {
let hapiFallback = 0;
if (events.length === 0 && data.hapiSummary) {
const h = data.hapiSummary;
hapiFallback = Math.min(60, h.eventsPoliticalViolence * 3 * multiplier);
hapiFallback = Math.min(60, Math.log1p(h.eventsPoliticalViolence * multiplier) * 12);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 PR description math doesn't match the implementation

The description claims "Iran (1549 events, ×2.0 multiplier): log1p(3098)*12 ≈ 54", but Math.log1p(3098) * 12 = ln(3099) * 12 ≈ 8.04 * 12 ≈ 96.5, which still hits the Math.min(60, …) cap — giving Iran 60, not 54. Similarly, Syria is described as yielding ≈15 but log1p(14.7) * 12 ≈ 33.

If the stated runtime values (Iran: 1549 HAPI events, Syria: 21) are accurate, both old and new formulas cap Iran at 60, so the China–Iran gap narrows only because China's score drops from 60 to ~57 — not because Iran's score changes. The fix still achieves meaningful separation, but less dramatically than described. It's worth confirming the actual h.eventsPoliticalViolence values coming from HAPI to validate the expected output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — the runtime value examples in the original description had incorrect math. The PR description has been updated to remove the specific runtime value claims and instead describe the log-scale effect qualitatively: moderate and extreme event counts remain distinguishable after capping, preserving meaningful separation. The before/after formulas and displacement tier table are unchanged.

}

let newsFloor = 0;
Expand Down Expand Up @@ -971,6 +971,16 @@ function getTrend(code: string, current: number): CountryScore['trend'] {
return 'stable';
}

function getDisplacementBoost(outflow: number): number {
return outflow >= 10_000_000 ? 12
: outflow >= 5_000_000 ? 10
: outflow >= 1_000_000 ? 8
: outflow >= 500_000 ? 6
: outflow >= 100_000 ? 4
: outflow >= 10_000 ? 2
: 0;
}

export function calculateCII(): CountryScore[] {
const scores: CountryScore[] = [];
const focalUrgencies = focalPointDetector.getCountryUrgencyMap();
Expand Down Expand Up @@ -1003,9 +1013,7 @@ export function calculateCII(): CountryScore[] {
: focalUrgency === 'elevated' ? 4
: 0;

const displacementBoost = data.displacementOutflow >= 1_000_000 ? 8
: data.displacementOutflow >= 100_000 ? 4
: 0;
const displacementBoost = getDisplacementBoost(data.displacementOutflow);
const climateBoost = data.climateStress;

const advisoryBoost = getAdvisoryBoost(data);
Expand Down Expand Up @@ -1059,9 +1067,7 @@ export function getCountryScore(code: string): number | null {
const focalBoost = focalUrgency === 'critical' ? 8
: focalUrgency === 'elevated' ? 4
: 0;
const displacementBoost = data.displacementOutflow >= 1_000_000 ? 8
: data.displacementOutflow >= 100_000 ? 4
: 0;
const displacementBoost = getDisplacementBoost(data.displacementOutflow);
const climateBoost = data.climateStress;
const advisoryBoost = getAdvisoryBoost(data);
const supplementalSignalBoost = getSupplementalSignalBoost(data);
Expand Down
Loading