{"record":{"id":"c021a3d64556ec9d","repo":"affaan-m/ECC","slug":"genre-spec-get-number-has-invalid-total-duration","errorCode":null,"errorMessage":"genre {spec.get('number')} has invalid total duration","messagePattern":"genre (.+?) has invalid total duration","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":140,"sourceCode":"    if len(numbers) != len(set(numbers)):\n        raise ContractError(\"genre numbers must be distinct\")\n\n    fingerprints = [spec.get(\"style_fingerprint\") for spec in specs]\n    signatures = [_semantic_signature(spec) for spec in specs]\n    if len(fingerprints) != len(set(fingerprints)) or len(signatures) != len(set(signatures)):\n        raise ContractError(\"genre references collapsed into a generic style; distinct specs required\")\n\n    for spec in specs:\n        if spec.get(\"dry_run\") is not True:\n            raise ContractError(f\"genre {spec.get('number')} crosses the dry-run boundary\")\n        measured = spec.get(\"measured_features\")\n        if measured is not None:\n            if not isinstance(measured, dict):\n                raise ContractError(f\"genre {spec.get('number')} has invalid measured evidence\")\n            _validate_numeric_evidence(measured, label=f\"genre {spec.get('number')} evidence\")\n            total_duration = measured.get(\"total_duration\")\n            if not _is_finite_real(total_duration):\n                raise ContractError(f\"genre {spec.get('number')} has invalid total duration\")\n            total_duration = cast(float, total_duration)\n            if float(total_duration) <= 0:\n                raise ContractError(f\"genre {spec.get('number')} has invalid total duration\")\n            for group_name in (\"sample_times\",):\n                groups = measured.get(group_name, [])\n                if not isinstance(groups, list):\n                    raise ContractError(f\"genre {spec.get('number')} has invalid time evidence\")\n                for group in groups:\n                    if not isinstance(group, dict):\n                        raise ContractError(f\"genre {spec.get('number')} has invalid time evidence\")\n                    for time in group.get(\"times\", []):\n                        _validate_media_time(\n                            time, group.get(\"source_duration\"),\n                            label=f\"genre {spec.get('number')} time evidence\",\n                        )\n            temporal = measured.get(\"temporal\", {})\n            if isinstance(temporal, dict):\n                for group in temporal.get(\"scene_change_evidence\", []):","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L122-L158","documentation":"When a spec's measured_features contains total_duration, it must be a finite real number. _is_finite_real rejects None, non-numeric types, NaN, and infinity. The validator raises this ContractError so downstream timing math never operates on missing or non-finite durations.","triggerScenarios":"Calling validate_genre_specs / validate_bundle with measured_features containing total_duration set to None, a string like \"3.5\", NaN/inf (e.g. from a failed measurement or 0-division), or the key holding a non-numeric object.","commonSituations":"A measurement step that failed and wrote NaN (e.g. dividing by zero duration); JSON configs where total_duration was quoted as a string; missing key resolved via .get to None; serialization of float('inf') from earlier computations.","solutions":["Ensure the measurement pipeline produces a real finite float for total_duration and fix the NaN/inf source (often a division by zero).","Parse string durations: total_duration = float(raw) after validating it is numeric.","If no duration is known yet, omit the key or set measured_features to None rather than passing NaN.","Add math.isfinite(total_duration) at the point where evidence is recorded to fail early."],"exampleFix":"// before\nspec[\"measured_features\"][\"total_duration\"] = float(\"nan\")\n// after\nimport math\nduration = compute_duration()\nassert math.isfinite(duration) and duration > 0\nspec[\"measured_features\"][\"total_duration\"] = duration","handlingStrategy":"validation","validationCode":"import math\n\ndef duration_is_finite(spec):\n    m = spec.get(\"measured_features\") or {}\n    d = m.get(\"total_duration\")\n    return isinstance(d, (int, float)) and not isinstance(d, bool) and math.isfinite(d)","typeGuard":"def is_finite_duration(value) -> bool:\n    return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)","tryCatchPattern":"try:\n    validate_genre_specs(specs)\nexcept ContractError as e:\n    if \"invalid total duration\" in str(e):\n        for s in specs:\n            d = (s.get(\"measured_features\") or {}).get(\"total_duration\")\n            if not math.isfinite(float(d)):\n                raise ValueError(f\"re-measure spec {s.get('number')}: duration={d!r}\")\n    raise","preventionTips":["Fail fast with math.isfinite() where the duration is measured.","Investigate NaN/inf sources — usually division by zero in the measurement code.","Never serialize float('inf') or float('nan') into evidence files.","Parse quoted duration strings to float before storing."],"tags":["validation","nan","duration"],"backgroundTag":"invalid-duration-format","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}