{"record":{"id":"2a21f15bda9f822b","repo":"affaan-m/ECC","slug":"genre-spec-get-number-has-invalid-measured-evidence","errorCode":null,"errorMessage":"genre {spec.get('number')} has invalid measured evidence","messagePattern":"genre (.+?) has invalid measured evidence","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":136,"sourceCode":"    \"\"\"Require complete, semantically distinct numbered genre specs.\"\"\"\n    if not specs:\n        raise ContractError(\"at least one genre spec is required\")\n    numbers = [spec.get(\"number\") for spec in specs]\n    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\",","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L118-L154","documentation":"If a genre spec includes measured_features, that value must be a dict of numeric evidence. The validator raises this ContractError when measured_features is present but not a mapping (e.g. a list, string, or scalar). Setting it to None skips the check, so only non-null non-dict values trigger this.","triggerScenarios":"Calling validate_genre_specs / validate_bundle with a spec whose measured_features is a list of measurements, a JSON string of measurements, or a scalar number instead of a dict.","commonSituations":"A measurement tool serializing evidence as a JSON string instead of a parsed object; refactored code that switched measured_features from dict to list; hand-edited configs where quotes around the JSON block were left in place.","solutions":["Make measured_features a dict of numeric evidence, e.g. {\"total_duration\": 180.0, ...}.","If you have a JSON string, json.loads() it before assigning.","If you have a list, restructure it into a keyed dict matching the expected evidence schema.","If there is genuinely no measurement yet, remove the key or set it to null instead of passing an empty/wrong-typed value."],"exampleFix":"// before\nspec[\"measured_features\"] = json.dumps({\"total_duration\": 90.0})\n// after\nspec[\"measured_features\"] = {\"total_duration\": 90.0}","handlingStrategy":"type-guard","validationCode":"def measured_is_dict(spec):\n    m = spec.get(\"measured_features\")\n    return m is None or isinstance(m, dict)","typeGuard":"def is_measured_features(value) -> bool:\n    return value is None or isinstance(value, dict)","tryCatchPattern":"try:\n    validate_genre_specs(specs)\nexcept ContractError as e:\n    if \"invalid measured evidence\" in str(e):\n        spec = next(s for s in specs if not isinstance(s.get(\"measured_features\"), (dict, type(None))))\n        spec[\"measured_features\"] = json.loads(spec[\"measured_features\"])\n    raise","preventionTips":["Parse measurement JSON at ingestion; never store it as a string.","Use None (not {} or []) when evidence is absent.","Type-check measured_features where it is produced, not only at validation.","Keep the evidence schema documented next to the producer code."],"tags":["validation","type-mismatch","measured-evidence"],"backgroundTag":"type-mismatch","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"}