{
    "version": 1,
    "description": "Kinometric balance scoring parameters. Change values here and recompile to adjust scoring without code changes.",

    "scoring_curve": {
        "description": "Piecewise linear interpolation curve. x=q_diff_range, y=score. Must be same length and x must be ascending.",
        "knots_x": [0.00137, 0.003455, 0.006777, 0.012668, 0.02, 0.03],
        "knots_y": [10, 9, 7, 5, 4, 3]
    },

    "csv_parsing": {
        "description": "How to read the sensor CSV file.",
        "header_rows": 3,
        "q_diff_column": 5,
        "sensortime_column": 7,
        "sensortime_unit": "milliseconds"
    },

    "analysis": {
        "description": "Core analysis parameters.",
        "start_row_skip": 200,
        "min_samples": 300,
        "target_windows_seconds": [3, 5],
        "aggregation": "min",
        "outlier_percentile": 98,
        "stable_cap_threshold": 0.005,
        "stable_cap_score": 10.0
    },

    "stability": {
        "description": "Stability calculation thresholds.",
        "stable_threshold": 0.007
    },

    "rescale": {
        "description": "Score rescaling. Use 'none' for production (score == raw).",
        "type": "none"
    },

    "risk_thresholds": {
        "description": "Score-to-risk level mapping.",
        "critical_below": 4,
        "high_below": 6,
        "moderate_below": 8,
        "low_at_or_above": 8
    },

    "fatigue": {
        "description": "Fatigue pattern detection thresholds.",
        "fatigues_min_short_term": 7,
        "fatigues_min_drop": 3,
        "consistent_min_short_term": 7,
        "consistent_max_drop": 3,
        "unstable_max_short_term": 7,
        "unstable_max_drop": 2
    },

    "duration_penalty": {
        "description": "Penalty subtracted when test too short for max window. 3_second_only applies if only smallest window exists. Only used in qdiff mode.",
        "3_second_only": 3,
        "5_second_max": 0,
        "7_second_available": 0
    },

    "seven_second_bonus": {
        "description": "Score capped at max_without_7s unless 7s window q-diff data exists. If 7s window exists, bonus tiers raise the cap based on q-diff range.",
        "enabled": true,
        "max_without_7s": 7,
        "tiers": [
            { "max_qdiff": 0.002, "score": 10 },
            { "max_qdiff": 0.003, "score": 9 },
            { "max_qdiff": 0.005, "score": 8 }
        ]
    },

    "test_recording": {
        "description": "Live recording indicators. Duration = simple timer. Stability = longest continuous stable stretch at stable_threshold.",
        "min_duration_seconds": 15,
        "stable_target_seconds": 7.0,
        "check_interval_ms": 500
    },

    "scoring_mode": "duration",
    "scoring_mode_description": "qdiff = score from q-diff range (default). duration = score from longest stable stretch in seconds.",

    "duration_scoring": {
        "description": "When scoring_mode is 'duration', map longest stable stretch (seconds) to 0-10 score via piecewise linear curve.",
        "knots_x": [0, 3, 5, 7, 10],
        "knots_y": [0, 4, 6, 8, 10],
        "quality_bonus": {
            "enabled": true,
            "threshold_pct": 0.5,
            "bonus": 1.0
        }
    }
}
