{"record":{"id":"09d3d28bb59bbe36","repo":"affaan-m/ECC","slug":"placement-id-must-be-unique-and-nonempty","errorCode":null,"errorMessage":"placement id must be unique and nonempty","messagePattern":"placement id must be unique and nonempty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/resolve.py","lineNumber":100,"sourceCode":"        \"fps\": str(average),\n        \"frames\": int(stream[\"nb_read_frames\"]),\n        \"has_alpha\": alpha,\n    }\n\n\ndef allocate_placements(placements, *, fps, base_track_count, probe=probe_asset):\n    \"\"\"Validate assets and interval-color overlays above preserved video tracks.\n\n    record_frame is an absolute timeline frame; intervals are [start, end).\n    Optional requires_alpha=True enforces a decoded alpha-capable pixel format.\n    \"\"\"\n    rate = _fps(fps)\n    _integer(base_track_count, \"base_track_count\")\n    checked, seen, metadata = [], set(), {}\n    for event in placements:\n        identifier = event.get(\"id\")\n        if not isinstance(identifier, str) or not identifier or identifier in seen:\n            raise ValueError(\"placement id must be unique and nonempty\")\n        seen.add(identifier)\n        start = _integer(event.get(\"record_frame\"), \"record_frame\")\n        frames = _integer(event.get(\"frames\"), \"frames\", 1)\n        opacity = event.get(\"opacity\")\n        if (\n            isinstance(opacity, bool)\n            or not isinstance(opacity, (int, float))\n            or not math.isfinite(opacity)\n            or not 0 <= opacity <= 100\n        ):\n            raise ValueError(\"explicit opacity must be finite within 0..100\")\n        composite = _integer(event.get(\"composite\"), \"composite\")\n        raw_asset = event.get(\"asset\")\n        if not isinstance(raw_asset, (str, Path)) or not str(raw_asset):\n            raise ValueError(\"asset must be a local regular file\")\n        asset = Path(raw_asset).expanduser().resolve()\n        if not asset.is_file():\n            raise ValueError(f\"asset must be a local regular file: {asset}\")","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/resolve.py#L82-L118","documentation":"allocate_placements validates each placement event dict; it raises ValueError('placement id must be unique and nonempty') when an event's 'id' is missing, not a string, an empty string, or duplicates an id already seen in the same call. Ids identify composited placements downstream, so they must be present and unique.","triggerScenarios":"Calling allocate_placements/apply_placements/plan with placements where one event lacks 'id', has id='' or id=None, has a numeric id, or two events share the same id (e.g. a template loop forgot to uniquify ids).","commonSituations":"Programmatically generated placement lists reusing the default id across iterations; JSON configs with a typo'd key ('Id' vs 'id'); copying a placement entry without renaming its id.","solutions":["Ensure every placement dict has a nonempty string 'id' and that ids are unique within the list","When generating placements in a loop, append an index or uuid: f'overlay-{i}' or str(uuid4())","Check for casing/key typos: the key must be exactly 'id'","Deduplicate or rename conflicting ids before calling, e.g. by prefixing duplicates"],"exampleFix":"# before\nplacements = [{'id': 'logo', 'asset': 'a.webm'}, {'id': 'logo', 'asset': 'b.webm'}]\n# after\nplacements = [{'id': 'logo-a', 'asset': 'a.webm'}, {'id': 'logo-b', 'asset': 'b.webm'}]","handlingStrategy":"validation","validationCode":"def validate_placement_ids(placements):\n    seen = set()\n    for ev in placements:\n        pid = ev.get('id')\n        if not isinstance(pid, str) or not pid or pid in seen:\n            raise ValueError(f'bad placement id: {pid!r}')\n        seen.add(pid)\n\nvalidate_placement_ids(placements)","typeGuard":"def has_valid_id(ev) -> bool:\n    pid = ev.get('id')\n    return isinstance(pid, str) and bool(pid)","tryCatchPattern":"try:\n    resolve.allocate_placements(fps, base_track_count, placements)\nexcept ValueError as e:\n    if 'placement id' in str(e):\n        raise PlacementError('duplicate or missing placement id') from e\n    raise","preventionTips":["Generate ids programmatically with an index/uuid in loops","Lint placement JSON for duplicate ids before rendering","Keep the key spelled exactly 'id' in configs"],"tags":["validation","placements","identifier"],"backgroundTag":"invalid-identifier-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"}