set-diversity-policy.mjs 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. const asArray = (value) => Array.isArray(value) ? value : [];
  2. const text = (value, fallback = "") => typeof value === "string" && value.trim() ? value.trim() : fallback;
  3. const wearingRelations = new Set(["human_wearing", "human_wearing_static", "human_wearing_action"]);
  4. export function compileSetDiversityPolicy(scenePolicy = {}) {
  5. const supportMode = text(scenePolicy.support_mode, "installed_or_environmental");
  6. const wearable = supportMode === "body_worn" || supportMode === "floor_supported" && scenePolicy.wearable_subtype === "footwear";
  7. return {
  8. schema_version: "1.0",
  9. minimum_distinct_content_signatures: 5,
  10. maximum_same_axis_framing: 2,
  11. maximum_human_wearing_pages: Number(scenePolicy.set_budget?.maximum_human_wearing_pages ?? (wearable ? 3 : 0)),
  12. required_relation_families: wearable
  13. ? ["isolated", "detail", "stable_display", "human_proximity", "human_wearing"]
  14. : ["isolated", "detail", "environmental", "human_context"],
  15. allowed_relation_modes: asArray(scenePolicy.allowed_relation_modes).length
  16. ? [...scenePolicy.allowed_relation_modes]
  17. : wearable
  18. ? ["isolated", "detail", "stable_display", "human_proximity", "human_wearing_static", "human_wearing_action"]
  19. : ["isolated", "detail", "stable_display", "environmental", "human_proximity", "human_handheld"],
  20. automatic_paid_regeneration: false,
  21. };
  22. }
  23. export function relationFamily(value = "") {
  24. const relation = text(value, "unknown");
  25. if (wearingRelations.has(relation)) return "human_wearing";
  26. if (relation.includes("detail") || relation.includes("material") || relation.includes("feature")) return "detail";
  27. if (relation.includes("isolated") || relation.includes("single accurate")) return "isolated";
  28. if (relation.includes("proximity")) return "human_proximity";
  29. if (relation.includes("handheld")) return "human_context";
  30. if (relation.includes("environment") || relation.includes("tabletop") || relation.includes("support") || relation.includes("display")) return "stable_display";
  31. return relation;
  32. }
  33. export function contentSignatureForPage({ page = {}, relationMode = "", index = 0 } = {}) {
  34. const shot = page.shot_spec || {};
  35. const evidence = asArray(page.evidence_refs).join("+") || `page:${index + 1}`;
  36. return [
  37. text(page.business_category, text(page.role, "page")),
  38. text(page.set_design?.render_family, "render"),
  39. text(shot.camera_axis, "axis"),
  40. text(shot.framing_mode, "framing"),
  41. text(relationMode || shot.interaction_mode, "relation"),
  42. evidence,
  43. ].join("|");
  44. }
  45. export function summarizePlannedDiversity(images = [], policy = {}) {
  46. const signatures = asArray(images).map((page) => text(page.set_design?.content_signature));
  47. const axisFraming = asArray(images).map((page) => `${text(page.shot_spec?.camera_axis)}|${text(page.shot_spec?.framing_mode)}`);
  48. const wearing = asArray(images).filter((page) => relationFamily(page.expected_page_compliance?.product_human_relation) === "human_wearing").length;
  49. const counts = (values) => Object.fromEntries([...new Set(values)].map((value) => [value, values.filter((candidate) => candidate === value).length]));
  50. return {
  51. distinct_content_signatures: new Set(signatures.filter(Boolean)).size,
  52. axis_framing_counts: counts(axisFraming),
  53. human_wearing_pages: wearing,
  54. policy: policy.schema_version ? policy : compileSetDiversityPolicy(policy),
  55. };
  56. }