{"record":{"id":"c903d55dcec28358","repo":"affaan-m/ECC","slug":"native-fps-missing","errorCode":null,"errorMessage":"native fps missing","messagePattern":"native fps missing","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":202,"sourceCode":"        raise ValueError(\"invalid JSON artifact\") from exc\n\n\ndef _snapshot(baseline: dict) -> dict:\n    _object(baseline, {\"project_file\", \"snapshot_file\", \"project_name\", \"timeline_name\",\n                       \"fps\", \"timeline_range\"})\n    _rate(baseline[\"fps\"])\n    bounds = _range(baseline[\"timeline_range\"])\n    _artifact(baseline[\"project_file\"])\n    snapshot = _artifact(baseline[\"snapshot_file\"], parse_json=True)\n    if not isinstance(snapshot, dict):\n        raise ValueError(\"native snapshot must be an object\")\n    settings = snapshot.get(\"settings\")\n    native_fps = settings.get(\"timelineFrameRate\") if isinstance(settings, dict) else None\n    # Resolve's conventional decimal NTSC labels represent these exact rates.\n    ntsc = {\"23.976\": \"24000/1001\", \"29.97\": \"30000/1001\", \"59.94\": \"60000/1001\"}\n    try:\n        if type(native_fps) not in (str, int, float):\n            raise ValueError(\"native fps missing\")\n        label = str(native_fps)\n        if not re.fullmatch(r\"[0-9]{1,9}(?:\\.[0-9]{1,12}|/[1-9][0-9]{0,8})?\", label):\n            raise ValueError(\"native fps must use a bounded decimal or rational label\")\n        native_rate = Fraction(ntsc.get(label, label))\n        if native_rate != Fraction(*_rate(baseline[\"fps\"])):\n            raise ValueError(\"native fps differs\")\n    except (ValueError, ZeroDivisionError) as exc:\n        raise ValueError(\"native snapshot fps is missing, invalid or contradictory\") from exc\n    for cfg, key in [(\"project_name\", \"project\"), (\"timeline_name\", \"timeline\")]:\n        if _text(baseline[cfg]) != snapshot.get(key):\n            raise ValueError(\"native snapshot identity mismatch\")\n    tracks = snapshot.get(\"timeline_readback\")\n    if not isinstance(tracks, dict) or not tracks:\n        raise ValueError(\"native clip snapshot required\")\n    for track, clips in tracks.items():\n        if not re.fullmatch(r\"(?:video|audio)[1-9][0-9]*\", track) or not isinstance(clips, list):\n            raise ValueError(\"invalid native snapshot track\")\n        for clip in clips:","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L184-L220","documentation":"_snapshot reads settings.timelineFrameRate from the native snapshot and requires it to be a string, int or float. If settings is missing, not a dict, or timelineFrameRate is absent/null/bool, the library raises \"native fps missing\" (immediately wrapped into the composite fps error).","triggerScenarios":"build_application_bundle where the snapshot JSON lacks a `settings` object, or settings exists but has no `timelineFrameRate` key, or its value is null/bool/list/dict.","commonSituations":"Snapshot exported from a different tool that uses a different key name (e.g. frameRate, fps); an older Resolve version with a different settings layout; a hand-trimmed snapshot where the settings block was deleted.","solutions":["Add or restore `\"settings\": {\"timelineFrameRate\": <rate>}` in the snapshot JSON, using the same label Resolve exports (e.g. \"23.976\", \"24\", or \"24000/1001\").","Re-export the snapshot from the correct Resolve version so settings.timelineFrameRate is present.","Check the key spelling/casing — it must be exactly `timelineFrameRate`.","Make sure the value is a plain string/number, not null, true/false, or an object."],"exampleFix":"// before\n{\"project\": \"P\", \"timeline\": \"T\"}\n\n// after\n{\"project\": \"P\", \"timeline\": \"T\", \"settings\": {\"timelineFrameRate\": \"23.976\"}}","handlingStrategy":"validation","validationCode":"def has_native_fps(snapshot):\n    settings = snapshot.get(\"settings\")\n    return isinstance(settings, dict) and type(settings.get(\"timelineFrameRate\")) in (str, int, float)","typeGuard":"def has_fps(snapshot) -> bool:\n    s = snapshot.get(\"settings\") if isinstance(snapshot, dict) else None\n    return isinstance(s, dict) and type(s.get(\"timelineFrameRate\")) in (str, int, float)","tryCatchPattern":"try:\n    bundle = build_application_bundle(config, None, local_only=True)\nexcept ValueError as exc:\n    if \"fps is missing, invalid or contradictory\" in str(exc):\n        print(\"snapshot settings.timelineFrameRate missing or wrong type\")\n    raise","preventionTips":["Never strip the settings block from snapshots.","Assert timelineFrameRate exists in export tests.","Use exact Resolve export output without hand edits.","Check key casing (timelineFrameRate) in the exporter."],"tags":["validation","snapshot","fps"],"backgroundTag":"missing-required-config-field","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"}