{"record":{"id":"d8f168f6103858e2","repo":"affaan-m/ECC","slug":"fps-must-be-a-reduced-positive-rational","errorCode":null,"errorMessage":"fps must be a reduced positive rational","messagePattern":"fps must be a reduced positive rational","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":63,"sourceCode":"\n\ndef _text(value: Any) -> str:\n    if not isinstance(value, str) or not value.strip():\n        raise ValueError(\"nonempty text required\")\n    return value\n\n\ndef _integer(value: Any, minimum: int = 0) -> int:\n    if type(value) is not int or value < minimum:\n        raise ValueError(\"frame/count must be an exact integer in range\")\n    return value\n\n\ndef _rate(value: Any) -> tuple[int, int]:\n    _object(value, {\"numerator\", \"denominator\"})\n    n, d = (_integer(value[k], 1) for k in (\"numerator\", \"denominator\"))\n    if math.gcd(n, d) != 1:\n        raise ValueError(\"fps must be a reduced positive rational\")\n    return n, d\n\n\ndef _range(value: Any, bounds: list[int] | None = None) -> list[int]:\n    if not isinstance(value, list) or len(value) != 2:\n        raise ValueError(\"range must contain two frame integers\")\n    start, end = (_integer(v) for v in value)\n    if start >= end or (bounds is not None and (start < bounds[0] or end > bounds[1])):\n        raise ValueError(\"frame range is empty or outside its bounds\")\n    return value\n\n\ndef _overlap(a: list[int], b: list[int]) -> bool:\n    return a[0] < b[1] and b[0] < a[1]\n\n\ndef _sha(value: Any) -> str:\n    if not isinstance(value, str) or not re.fullmatch(r\"[a-f0-9]{64}\", value):","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L45-L81","documentation":"_rate validates an fps object as a {numerator, denominator} pair of positive integers and additionally requires gcd(n, d) == 1, i.e. the rational must be in lowest terms. Non-reduced fractions like 48/2 raise ValueError('fps must be a reduced positive rational').","triggerScenarios":"Building a bundle with fps given as 30000/1001 un-normalized after multiplication, or any numerator/denominator sharing a common factor (e.g. 24/2 instead of 12/1).","commonSituations":"Computing rates by multiplying/editing rationals without re-reducing, copying rates from tools that emit unreduced fractions, or hand-writing 30000/1001-style rates after scaling.","solutions":["Reduce the fraction: divide both parts by math.gcd(numerator, denominator) before inserting into the bundle","Use Fraction(n, d) and write Fraction.numerator/.denominator, which are always reduced","Ensure both parts are >= 1 (zero or negative parts also fail _integer)"],"exampleFix":"// before\nfps = {\"numerator\": 48, \"denominator\": 2}\n// after\nfrom math import gcd\nfps = {\"numerator\": 48 // gcd(48, 2), \"denominator\": 2 // gcd(48, 2)}  # 24/1","handlingStrategy":"validation","validationCode":"from math import gcd\ndef reduced_rate(n, d):\n    assert n >= 1 and d >= 1 and gcd(n, d) == 1, f\"unreduced rate {n}/{d}\"\n    return {\"numerator\": n, \"denominator\": d}","typeGuard":null,"tryCatchPattern":"try:\n    bundle = build_application_bundle(data)\nexcept ValueError as e:\n    if \"reduced positive rational\" in str(e):\n        print(\"reduce the fps fraction with gcd before building the bundle\")\n    else:\n        raise","preventionTips":["Always build rates via Fraction(n, d) and read .numerator/.denominator","Re-reduce rates after any arithmetic (speed changes, concatenations)","Assert gcd == 1 in rate-producing helper functions"],"tags":["rational","fps","validation"],"backgroundTag":"invalid-argument-value","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"}