{"record":{"id":"2903b2c34c659bb1","repo":"JuliusBrussee/caveman","slug":"where-probability-a-given-b-relationship-probability-a-given","errorCode":null,"errorMessage":"${where} probability_a_given_b ${relationship.probability_a_given_b} != ${expectedAGivenB}","messagePattern":"(.+?) probability_a_given_b (.+?) != (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/contracts/scripts/validate-continuous-improvement.mjs","lineNumber":99,"sourceCode":"    if (!themeIds.has(relationship.theme_a_id) || !themeIds.has(relationship.theme_b_id)) {\n      throw new Error(`${where} references a theme that is not in this report`);\n    }\n    if (!(relationship.theme_a_id < relationship.theme_b_id)) {\n      throw new Error(`${where} theme ids are not in canonical order`);\n    }\n    if (relationship.shared_unit_count > relationship.theme_a_unit_count || relationship.shared_unit_count > relationship.theme_b_unit_count) {\n      throw new Error(`${where} shares more units than either theme has`);\n    }\n    if (relationship.shared_unit_ids.length > relationship.shared_unit_count) {\n      throw new Error(`${where} carries more shared unit ids than its shared unit count`);\n    }\n    const expectedBGivenA = relationship.shared_unit_count / relationship.theme_a_unit_count;\n    const expectedAGivenB = relationship.shared_unit_count / relationship.theme_b_unit_count;\n    if (Math.abs(relationship.probability_b_given_a - expectedBGivenA) > 1e-9) {\n      throw new Error(`${where} probability_b_given_a ${relationship.probability_b_given_a} != ${expectedBGivenA}`);\n    }\n    if (Math.abs(relationship.probability_a_given_b - expectedAGivenB) > 1e-9) {\n      throw new Error(`${where} probability_a_given_b ${relationship.probability_a_given_b} != ${expectedAGivenB}`);\n    }\n  }\n  relationshipCount += report.relationships.length;\n\n  // A motif is a structural count over variants this report carries, so every\n  // part of it must be re-derivable from those variants.\n  const variantsByID = new Map(report.workflow_variants.map((variant) => [variant.id, variant]));\n  const familyIDs = new Set(report.task_families.map((family) => family.id));\n  for (const motif of report.motifs) {\n    const where = `report fixture ${reportPaths[index]}: motif ${motif.id}`;\n    if (!familyIDs.has(motif.task_family_id)) throw new Error(`${where} references a task family that is not in this report`);\n    if (motif.support_variant_count !== motif.variant_ids.length) throw new Error(`${where} support variant count disagrees with its variant ids`);\n    let runs = 0;\n    let weighted = 0;\n    for (const variantID of motif.variant_ids) {\n      const variant = variantsByID.get(variantID);\n      if (!variant) throw new Error(`${where} references workflow variant ${variantID} that is not in this report`);\n      if (variant.task_family_id !== motif.task_family_id) throw new Error(`${where} supporting variant ${variantID} belongs to another task family`);","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/shared/contracts/scripts/validate-continuous-improvement.mjs#L81-L117","documentation":"Symmetric to probability_b_given_a: the validator requires probability_a_given_b to equal shared_unit_count / theme_b_unit_count within 1e-9. This keeps the relationship's reverse conditional probability derivable from the fixture's own counts.","triggerScenarios":"A relationship entry where |probability_a_given_b - (shared_unit_count / theme_b_unit_count)| > 1e-9, typically after changing theme_b_unit_count or the shared count without updating the reverse probability.","commonSituations":"Only updating one direction's probability after editing counts; copy-pasting a relationship and swapping A/B fields inconsistently; hand-rounded probability values.","solutions":["Recompute probability_a_given_b as shared_unit_count / theme_b_unit_count and update the fixture.","Fix theme_b_unit_count or shared_unit_count if the counts are the stale fields.","Verify both directional probabilities together whenever any count changes."],"exampleFix":"// before\n{ \"shared_unit_count\": 2, \"theme_b_unit_count\": 5, \"probability_a_given_b\": 0.5 }\n// after\n{ \"shared_unit_count\": 2, \"theme_b_unit_count\": 5, \"probability_a_given_b\": 0.4 }","handlingStrategy":"validation","validationCode":"const expected = rel.shared_unit_count / rel.theme_b_unit_count;\nif (Math.abs(rel.probability_a_given_b - expected) > 1e-9) throw new Error(`probability_a_given_b must be ${expected}`);","typeGuard":"const probAGivenBValid = (rel) => Math.abs(rel.probability_a_given_b - rel.shared_unit_count / rel.theme_b_unit_count) <= 1e-9;","tryCatchPattern":"try { validateContinuousImprovement(report); } catch (e) { if (String(e.message).includes('probability_a_given_b')) { console.error('Recompute probability_a_given_b = shared_unit_count / theme_b_unit_count'); } else throw e; }","preventionTips":["Always update both directional probabilities together when counts change","Compute probabilities programmatically from counts at generation time","Swap A/B fields carefully when cloning relationships"],"tags":["fixture","probability","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}