diff --git a/frontend/src/components/rankings/WeightsForm.tsx b/frontend/src/components/rankings/WeightsForm.tsx index 9b43274..eaf2d6c 100644 --- a/frontend/src/components/rankings/WeightsForm.tsx +++ b/frontend/src/components/rankings/WeightsForm.tsx @@ -1,12 +1,16 @@ -import { useState, useMemo, type FormEvent } from 'react'; +import { useState, type FormEvent } from 'react'; import { useUpdateWeights } from '../../hooks/useScores'; interface WeightsFormProps { weights: Record; } +const SENTIMENT = 'sentiment'; + export function WeightsForm({ weights }: WeightsFormProps) { - // Convert API decimal weights (0-1) to 0-100 integer scale on mount + // API decimal weights (0-1) → 0-100 integer sliders. For the base dimensions + // that's their share of the weighted average; for sentiment it's the ± points + // it can move the composite (MAX_ADJ), decoupled from the base mix. const [sliderValues, setSliderValues] = useState>(() => Object.fromEntries( Object.entries(weights).map(([key, w]) => [key, Math.round(w * 100)]) @@ -14,10 +18,10 @@ export function WeightsForm({ weights }: WeightsFormProps) { ); const updateWeights = useUpdateWeights(); - const allZero = useMemo( - () => Object.values(sliderValues).every((v) => v === 0), - [sliderValues] - ); + const baseKeys = Object.keys(weights).filter((k) => k !== SENTIMENT); + const hasSentiment = SENTIMENT in weights; + const baseTotal = baseKeys.reduce((sum, k) => sum + (sliderValues[k] ?? 0), 0); + const sentimentPts = sliderValues[SENTIMENT] ?? 0; const handleChange = (key: string, value: string) => { const num = parseInt(value, 10); @@ -26,24 +30,35 @@ export function WeightsForm({ weights }: WeightsFormProps) { const handleSubmit = (e: FormEvent) => { e.preventDefault(); - if (allZero) return; + if (baseTotal === 0) return; - const total = Object.values(sliderValues).reduce((sum, v) => sum + v, 0); - const normalized = Object.fromEntries( - Object.entries(sliderValues).map(([key, v]) => [key, v / total]) + // Base dimensions normalize among themselves; sentiment passes through raw + // (slider value / 100) so it stays independent of the base. + const payload: Record = Object.fromEntries( + baseKeys.map((key) => [key, (sliderValues[key] ?? 0) / baseTotal]) ); - updateWeights.mutate(normalized); + if (hasSentiment) payload[SENTIMENT] = sentimentPts / 100; + updateWeights.mutate(payload); }; return (
-

+

Scoring Weights

+

+ The base dimensions are a weighted average (shares normalize to 100%). Sentiment is applied + separately as a signed adjustment on top. +

- {Object.keys(weights).map((key) => ( + {baseKeys.map((key) => (