Files
ComfyUI-Ethanfel-Prompt-Bui…/normal_camera_atlas_prep.py

2401 lines
104 KiB
Python

"""No-generation prep artifacts for the normal-camera atlas."""
from __future__ import annotations
import argparse
import html
import json
import re
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parent
CATALOG_DIR = ROOT / "categories"
DEFAULT_OUTPUT_DIR = ROOT / "ab_batches" / "normal_camera"
PRIORITY_PLAN_SCHEMA = "sxcp_normal_camera_priority_plan_v1"
PROMPT_CUE_BATCH_SCHEMA = "sxcp_normal_camera_prompt_cue_batch_v1"
SCORE_SHEET_SCHEMA = "sxcp_normal_camera_score_sheet_v1"
UNUSED_POOL_BACKLOG_SCHEMA = "sxcp_normal_camera_unused_pool_backlog_v1"
REVIEW_MANIFEST_SCHEMA = "sxcp_normal_camera_review_manifest_v1"
NEEDS_SAMPLES_ACQUISITION_SCHEMA = "sxcp_normal_camera_needs_samples_acquisition_v1"
DEFAULT_REVIEW_DIR = DEFAULT_OUTPUT_DIR / "review"
NEEDS_SAMPLES_TARGET_REFERENCE_COUNT = 12
DEFAULT_REVIEW_FOLDERS: tuple[str, ...] = (
"reverse cowgirl",
"breasts exposed",
"pussy spread",
"anal random",
"couple kissing",
"cowgirl - back view - 3-4 angle",
"cowgirl - side view",
"woman solo showing her hass - back view",
"doggy on all four - back view - 3-4 angle",
"doggy on all four - side view",
"doggy all four - front view",
"doggy - front view ",
"handjob standing - side view",
"wand",
"breast sucking - side view",
"fuck from front standing - side view",
"doggy - back view - 3-4 angle",
"penis worship",
"fuck from behind standing - woman backside - side view",
"piledriver",
"blowjob - laying - front view",
"breast - touching - front view",
"ballsucking - laying",
"ballsucking - standing",
"face sitting",
"pussy licking - backv iew",
"removing pants",
"rimjob",
"footjob",
"reverse cowgirl - leg up",
"reverse cowgirl -pretzel",
"fist",
"anal cowgirl",
"anal doggy - side view",
"anal fuck from behind laying - back view - 3-4 angle",
"anal reverse congress",
"anus lickiing",
"blowjob laying - back view - 3-4 angle",
"doggy press - back side",
"face sitting - front view",
"handjob - standing -low angle",
"pussy licking leg up - back view - 3-4 angle",
"pussy licking standing woman",
"under desk",
"pretzel",
"woman ass exposed",
"reverse congress - front view",
"boobjob",
"fingering",
"69",
)
REVIEW_BUCKET_VALUES: dict[str, str] = {
"back_view": "Rear-facing normal-camera view where back/hips face camera.",
"back_three_quarter": "Rear-offset normal-camera view with enough side/torso context.",
"side_view": "Profile or near-profile lateral view with the body/action axis across frame.",
"front_view": "Front-facing normal-camera view where the contact plane faces camera.",
"front_three_quarter": "Front-offset normal-camera view.",
"top_or_low_special": "Overhead, high-downward, low-angle, or under-view special camera.",
"reject_or_unclear": "POV-like, mismatched, unclear, duplicate-only, or too mixed for cue drafting.",
}
SOURCE_FOLDER_ALIASES: dict[str, dict[str, str]] = {
"doggy - front view": {
"canonical_folder": "doggy - front view",
"alias_reason": "Duplicate source-folder display name; preserved separately from trailing-space source folder.",
},
"doggy - front view ": {
"canonical_folder": "doggy - front view",
"alias_reason": "Trailing-space source folder preserved exactly for path stability.",
},
"handjob - standing -low angle": {
"canonical_folder": "handjob - standing - low angle",
"alias_reason": "Spacing typo normalized for review metadata while preserving exact source path.",
},
"pussy licking - backv iew": {
"canonical_folder": "pussy licking - back view",
"alias_reason": "Folder-name typo normalized for review metadata while preserving exact source path.",
},
"reverse cowgirl -pretzel": {
"canonical_folder": "reverse cowgirl - pretzel",
"alias_reason": "Missing-space folder label normalized for review metadata while preserving exact source path.",
},
"anus lickiing": {
"canonical_folder": "anus licking",
"alias_reason": "Folder-name typo normalized for review metadata while preserving exact source path.",
},
"woman solo showing her hass - back view": {
"canonical_folder": "woman solo showing her ass - back view",
"alias_reason": "Folder-name typo normalized for review metadata while preserving exact source path.",
},
}
REVIEW_SELECTED_SUBVARIANTS: dict[str, list[dict[str, Any]]] = {
"cowgirl - back view - 3-4 angle": [
{
"variant_key": "normal_cowgirl_back_three_quarter",
"review_bucket": "back_three_quarter",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"cowgirl - back view - 3-4 angle/0002.jpg",
"cowgirl - back view - 3-4 angle/0003_4.jpg",
"cowgirl - back view - 3-4 angle/0006.jpg",
"cowgirl - back view - 3-4 angle/0008_2.jpg",
"cowgirl - back view - 3-4 angle/16.jpg",
"cowgirl - back view - 3-4 angle/22.jpg",
"cowgirl - back view - 3-4 angle/41.jpg",
"cowgirl - back view - 3-4 angle/82.jpg",
"cowgirl - back view - 3-4 angle/90.jpg",
"cowgirl - back view - 3-4 angle/145.jpg",
"cowgirl - back view - 3-4 angle/160.jpg",
"cowgirl - back view - 3-4 angle/172.jpg",
"cowgirl - back view - 3-4 angle/225.jpg",
"cowgirl - back view - 3-4 angle/241_2.jpg",
"cowgirl - back view - 3-4 angle/248.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed the existing route as a broad but coherent "
"rear three-quarter cowgirl family. References were expanded rather than "
"splitting a near-duplicate route."
),
}
],
"cowgirl - side view": [
{
"variant_key": "normal_cowgirl_side_profile",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"cowgirl - side view/0003.jpg",
"cowgirl - side view/0004_2.jpg",
"cowgirl - side view/0005.jpg",
"cowgirl - side view/0007.jpg",
"cowgirl - side view/0009.jpg",
"cowgirl - side view/0011.jpg",
"cowgirl - side view/0015.jpg",
"cowgirl - side view/118.jpg",
"cowgirl - side view/195.jpg",
"cowgirl - side view/2.jpg",
"cowgirl - side view/200.jpg",
"cowgirl - side view/255.jpg",
"cowgirl - side view/280.jpg",
"cowgirl - side view/86.jpg",
"cowgirl - side view/86_2.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed the existing side-profile cowgirl route "
"as a coherent lateral camera family. References were expanded while "
"front-drifting, rear-drifting, and tight-crop examples remain outside "
"the selected subset."
),
}
],
"doggy on all four - back view - 3-4 angle": [
{
"variant_key": "normal_doggy_all_fours_back_three_quarter",
"review_bucket": "back_three_quarter",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"doggy on all four - back view - 3-4 angle/0002.jpg",
"doggy on all four - back view - 3-4 angle/0003.jpg",
"doggy on all four - back view - 3-4 angle/0013.jpg",
"doggy on all four - back view - 3-4 angle/0014.jpg",
"doggy on all four - back view - 3-4 angle/0019.jpg",
"doggy on all four - back view - 3-4 angle/0020.jpg",
"doggy on all four - back view - 3-4 angle/206.jpg",
"doggy on all four - back view - 3-4 angle/267.jpg",
"doggy on all four - back view - 3-4 angle/302.jpg",
"doggy on all four - back view - 3-4 angle/327.jpg",
"doggy on all four - back view - 3-4 angle/550_2.jpg",
"doggy on all four - back view - 3-4 angle/608_2.jpg",
"doggy on all four - back view - 3-4 angle/611.jpg",
"doggy on all four - back view - 3-4 angle/70.jpg",
"doggy on all four - back view - 3-4 angle/97.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed the existing all-fours rear three-quarter "
"route as a coherent camera family. References were expanded while low, "
"front-drifting, and tight-crop outliers remain outside the selected subset."
),
}
],
"doggy on all four - side view": [
{
"variant_key": "normal_doggy_all_fours_side_view",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-06-29",
"reference_images": [
"doggy on all four - side view/0020.jpg",
"doggy on all four - side view/192.jpg",
"doggy on all four - side view/2.jpg",
"doggy on all four - side view/204.jpg",
"doggy on all four - side view/254.jpg",
"doggy on all four - side view/254_2.jpg",
"doggy on all four - side view/254_3.jpg",
"doggy on all four - side view/271.jpg",
"doggy on all four - side view/271_2.jpg",
"doggy on all four - side view/362.jpg",
"doggy on all four - side view/495.jpg",
"doggy on all four - side view/556.jpg",
"doggy on all four - side view/86.jpg",
"doggy on all four - side view/99.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing selected side-view subset unchanged. "
"The remaining source images mix standing, bed, chair, close, and front-drifting "
"frames, so no additional references were promoted in this pass."
),
}
],
"doggy all four - front view": [
{
"variant_key": "normal_doggy_all_fours_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"doggy all four - front view/0016.jpg",
"doggy all four - front view/48.jpg",
"doggy all four - front view/58.jpg",
"doggy all four - front view/64.jpg",
"doggy all four - front view/69.jpg",
"doggy all four - front view/77.jpg",
"doggy all four - front view/83.jpg",
"doggy all four - front view/97.jpg",
"doggy all four - front view/171.jpg",
"doggy all four - front view/197.jpg",
"doggy all four - front view/204.jpg",
"doggy all four - front view/221.jpg",
"doggy all four - front view/290.jpg",
"doggy all four - front view/596.jpg",
"doggy all four - front view/598.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed a repeated front all-fours normal-camera "
"family with face and forearms readable in front while the partner remains "
"behind. Side-drifting, seated, tight-close, and vertical-crop outliers "
"remain outside the selected subset."
),
}
],
"doggy - front view ": [
{
"variant_key": "normal_doggy_generic_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"doggy - front view/11.jpg",
"doggy - front view/5.jpg",
"doggy - front view/6.jpg",
"doggy - front view /0003.jpg",
"doggy - front view /253.jpg",
"doggy - front view /262.jpg",
"doggy - front view /349.jpg",
"doggy - front view /354.jpg",
"doggy - front view /41.jpg",
"doggy - front view /49.jpg",
"doggy - front view /505.jpg",
"doggy - front view /537.jpg",
"doggy - front view /541.jpg",
"doggy - front view /567.jpg",
"doggy - front view /627.jpg",
],
"evidence_notes": (
"Residual-pool review expanded the existing grouped generic front-view route "
"for the duplicate trailing-space source folder. The selected references "
"repeat a front-facing third-person doggy camera while one weak top/down "
"close crop remains outside the route."
),
}
],
"wand": [
{
"variant_key": "normal_wand_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"wand/0005.jpg",
"wand/508.jpg",
"wand/75.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the folder as a mixed source-pool anchor. "
"The source images include useful front-close tool-contact material, but "
"side, portrait-close, and wider posture variation keep the full folder "
"from being one locked prompt-ready route."
),
},
{
"variant_key": "normal_wand_front_close_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"wand/0005.jpg",
"wand/0015.jpg",
"wand/217.jpg",
"wand/532.jpg",
"wand/561.jpg",
"wand/611.jpg",
"wand/75.jpg",
],
"evidence_notes": (
"Contact-sheet review found a repeated front-close third-person family "
"where the contact plane is centered and the wand remains readable as one "
"continuous object. Side, portrait-close, and wider posture outliers remain "
"outside this selected subset."
),
},
],
"handjob standing - side view": [
{
"variant_key": "normal_handjob_standing_side_profile",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-06-29",
"reference_images": [
"handjob standing - side view/16.jpg",
"handjob standing - side view/105.jpg",
"handjob standing - side view/171.jpg",
"handjob standing - side view/174.jpg",
"handjob standing - side view/175.jpg",
"handjob standing - side view/305.jpg",
"handjob standing - side view/549.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing standing side-profile subset unchanged. "
"The remaining source images mix kneeling, seated, close portrait, near-front, "
"and oral-adjacent frames, so no additional references were promoted in this pass."
),
}
],
"penis worship": [
{
"variant_key": "normal_penis_worship_foreground_close_reference_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"penis worship/0001.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing foreground-close source-pool anchor "
"unchanged. The remaining images are foreground-heavy and drift through front "
"or near-POV framing, so no prompt-ready expansion was promoted in this pass."
),
},
{
"variant_key": "normal_penis_worship_top_view",
"review_bucket": "top_or_low_special",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"penis worship/510.jpg",
"penis worship/517.jpg",
"penis worship/519.jpg",
"penis worship/520.jpg",
"penis worship/603_4.jpg",
"penis worship/607_2.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing user-selected top-view anchors "
"unchanged. The folder still needs cleaner repeated samples before this "
"orientation can become a pre-A/B route."
),
},
{
"variant_key": "normal_penis_worship_side_view",
"review_bucket": "side_view",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"penis worship/0003.jpg",
"penis worship/0004.jpg",
"penis worship/0008_2.jpg",
"penis worship/0016.jpg",
"penis worship/505.jpg",
"penis worship/566.jpg",
"penis worship/566_2.jpg",
"penis worship/618.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing user-selected side-view anchors "
"unchanged. The residual folder does not add a cleaner repeated lateral "
"camera subset in this pass."
),
},
{
"variant_key": "normal_penis_worship_laying_partner_vertical_side_view",
"review_bucket": "side_view",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"penis worship/0002_2.jpg",
"penis worship/0006.jpg",
"penis worship/0011.jpg",
"penis worship/608.jpg",
"penis worship/609.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing laying-partner vertical side-view "
"anchors unchanged. The remaining frames stay too mixed for promotion."
),
},
],
"piledriver": [
{
"variant_key": "normal_piledriver_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"piledriver/0001.jpg",
"piledriver/0012_2.jpg",
"piledriver/617.jpg",
],
"evidence_notes": (
"Residual-pool review preserved the existing mixed source-pool anchor. "
"The full folder remains too broad for one camera route because it mixes "
"tight top crops, side drifts, and selected high/front-down examples."
),
},
{
"variant_key": "normal_piledriver_high_front_down_view",
"review_bucket": "top_or_low_special",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"piledriver/0002.jpg",
"piledriver/0004_2.jpg",
"piledriver/0005.jpg",
"piledriver/0006_2.jpg",
"piledriver/0009.jpg",
"piledriver/0009_2.jpg",
"piledriver/0012.jpg",
"piledriver/0013.jpg",
"piledriver/282.jpg",
"piledriver/317.jpg",
"piledriver/317_2.jpg",
"piledriver/318.jpg",
"piledriver/319.jpg",
"piledriver/494_2.jpg",
"piledriver/538.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated high/front-down piledriver "
"camera family where the folded body remains readable below the partner. "
"Tight top crops, side drifts, and mixed examples remain outside this route."
),
},
],
"blowjob - laying - front view": [
{
"variant_key": "normal_blowjob_laying_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"blowjob - laying - front view/0001.jpg",
"blowjob - laying - front view/0003.jpg",
"blowjob - laying - front view/0004.jpg",
"blowjob - laying - front view/0010.jpg",
"blowjob - laying - front view/0010_2.jpg",
"blowjob - laying - front view/0018.jpg",
"blowjob - laying - front view/48.jpg",
"blowjob - laying - front view/69.jpg",
"blowjob - laying - front view/91.jpg",
"blowjob - laying - front view/94.jpg",
"blowjob - laying - front view/122.jpg",
"blowjob - laying - front view/171.jpg",
"blowjob - laying - front view/184.jpg",
"blowjob - laying - front view/274.jpg",
"blowjob - laying - front view/512.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed the existing front-facing laying "
"oral-contact route as a broad but coherent third-person camera family. "
"Side-drifting, wide couch/bed, and weak close-crop examples remain outside "
"the selected subset."
),
}
],
"breast - touching - front view": [
{
"variant_key": "normal_breast_contact_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"breast - touching - front view/0006.jpg",
"breast - touching - front view/0006_2.jpg",
"breast - touching - front view/12.jpg",
"breast - touching - front view/0014.jpg",
"breast - touching - front view/0015.jpg",
"breast - touching - front view/16.jpg",
"breast - touching - front view/56.jpg",
"breast - touching - front view/72.jpg",
"breast - touching - front view/183.jpg",
"breast - touching - front view/220.jpg",
"breast - touching - front view/246.jpg",
"breast - touching - front view/248.jpg",
"breast - touching - front view/281.jpg",
"breast - touching - front view/348.jpg",
"breast - touching - front view/361.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed the existing front breast-contact "
"route as a coherent third-person camera family across standing, "
"seated, and kneeling contexts. Side-drifting, costume/standing "
"outliers, and weak-contact crops remain outside the selected subset."
),
}
],
"ballsucking - standing": [
{
"variant_key": "normal_ballsucking_standing_partner_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"ballsucking - standing/0017.jpg",
"ballsucking - standing/252.jpg",
"ballsucking - standing/624.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the existing mixed source-pool anchor. "
"The standing-partner context is useful, but the full folder still mixes "
"close, side, front, and one water-close outlier; the outlier is anchored "
"here rather than in the pre-A/B split."
),
},
{
"variant_key": "normal_ballsucking_standing_low_side_view",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"ballsucking - standing/0014.jpg",
"ballsucking - standing/130.jpg",
"ballsucking - standing/137.jpg",
"ballsucking - standing/207.jpg",
"ballsucking - standing/222.jpg",
"ballsucking - standing/252.jpg",
"ballsucking - standing/333.jpg",
"ballsucking - standing/34.jpg",
"ballsucking - standing/540.jpg",
"ballsucking - standing/589.jpg",
"ballsucking - standing/624.jpg",
],
"evidence_notes": (
"Contact-sheet review found a repeated standing-partner, low-performer "
"side or near-side camera family. The water-close crop remains outside "
"this selected subset."
),
},
],
"face sitting": [
{
"variant_key": "normal_face_sitting_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"face sitting/0005.jpg",
"face sitting/0021.jpg",
"face sitting/354.jpg",
"face sitting/533.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the full folder as a mixed source-pool "
"anchor. Three samples are front or near-front face-sitting references, "
"while one rear/back-view outlier makes the folder too mixed and thin "
"for a selected pre-A/B route."
),
},
],
"pussy licking - backv iew": [
{
"variant_key": "normal_pussy_licking_backview_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"pussy licking - backv iew/220.jpg",
"pussy licking - backv iew/567.jpg",
"pussy licking - backv iew/614.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the full folder as a mixed source-pool "
"anchor. The folder label says back view, but the reviewed images read "
"as elevated front or high-oblique third-person oral-contact references; "
"the three-image set is too thin for a selected pre-A/B route."
),
},
],
"removing pants": [
{
"variant_key": "normal_removing_pants_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"removing pants/0005.jpg",
"removing pants/0006.jpg",
"removing pants/0016.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the full folder as a mixed source-pool "
"anchor. The three samples are near-duplicate close action references "
"with strong foreground crop, so they should not become a locked "
"normal-camera route without more matched, wider non-POV samples."
),
},
],
"ballsucking - laying": [
{
"variant_key": "normal_ballsucking_laying_close_reference_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"ballsucking - laying/271.jpg",
"ballsucking - laying/591.jpg",
],
"evidence_notes": (
"Contact-sheet review records the existing two-sample laying close-reference "
"source pool as needs_samples. The folder is fully represented but too thin "
"and crop-variable for a selected pre-A/B route."
),
},
],
"rimjob": [
{
"variant_key": "normal_rimjob_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"rimjob/0003.jpg",
"rimjob/0010.jpg",
"rimjob/0018.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the full folder as a mixed source-pool "
"anchor. The three samples mix close side/back oral-contact framing "
"with wider kneeling context and are too thin for a selected pre-A/B route."
),
},
],
"footjob": [
{
"variant_key": "normal_footjob_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"footjob/265.jpg",
"footjob/265_2.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the full folder as a mixed source-pool "
"anchor. The two samples share the foot-contact action but vary between "
"seated front context and tighter crop, so more matched samples are needed."
),
},
],
"reverse cowgirl - leg up": [
{
"variant_key": "normal_reverse_cowgirl_leg_up_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"reverse cowgirl - leg up/266.jpg",
"reverse cowgirl - leg up/267.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved this as a two-sample leg-up posture "
"source pool. The folder is useful cue evidence but too thin for a "
"selected normal-camera route."
),
},
],
"reverse cowgirl -pretzel": [
{
"variant_key": "normal_reverse_cowgirl_pretzel_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"reverse cowgirl -pretzel/0007.jpg",
"reverse cowgirl -pretzel/617.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved this as a two-sample pretzel-posture "
"source pool. The folder is useful cue evidence but too thin for a "
"selected normal-camera route."
),
},
],
"fist": [
{
"variant_key": "normal_fist_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"fist/337.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the one available manual-contact "
"reference as coverage evidence only. A single image cannot define "
"a stable normal-camera route."
),
},
],
"anal cowgirl": [
{
"variant_key": "normal_anal_cowgirl_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["anal cowgirl/49.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one available sample as coverage "
"evidence only. A single image cannot define a stable normal-camera route."
),
},
],
"anal doggy - side view": [
{
"variant_key": "normal_anal_doggy_side_view_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["anal doggy - side view/16.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one side-view sample as coverage "
"evidence only. The label is plausible, but a single image is too thin "
"for a selected pre-A/B route."
),
},
],
"anal fuck from behind laying - back view - 3-4 angle": [
{
"variant_key": "normal_anal_laying_back_three_quarter_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["anal fuck from behind laying - back view - 3-4 angle/14.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one rear three-quarter sample as "
"coverage evidence only. A single image cannot define a stable "
"normal-camera route."
),
},
],
"anal reverse congress": [
{
"variant_key": "normal_anal_reverse_congress_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["anal reverse congress/160.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one available sample as coverage "
"evidence only. A single image cannot define a stable normal-camera route."
),
},
],
"anus lickiing": [
{
"variant_key": "normal_anus_licking_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["anus lickiing/0004.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one available sample as coverage "
"evidence only. A single image cannot define a stable normal-camera route."
),
},
],
"blowjob laying - back view - 3-4 angle": [
{
"variant_key": "normal_blowjob_laying_back_three_quarter_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["blowjob laying - back view - 3-4 angle/14.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one rear-offset laying oral-contact "
"sample as coverage evidence only. A single image cannot define a stable "
"normal-camera route."
),
},
],
"doggy press - back side": [
{
"variant_key": "normal_doggy_press_back_side_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["doggy press - back side/0011.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one back-side doggy-press sample as "
"coverage evidence only. A single image cannot define a stable "
"normal-camera route."
),
},
],
"face sitting - front view": [
{
"variant_key": "normal_face_sitting_front_view_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["face sitting - front view/56.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one front-view face-sitting sample "
"as coverage evidence only. A single image is too thin for a selected "
"pre-A/B route."
),
},
],
"handjob - standing -low angle": [
{
"variant_key": "normal_handjob_standing_low_angle_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["handjob - standing -low angle/552.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one standing low-angle manual-contact "
"sample as coverage evidence only. A single image is too thin for a "
"selected pre-A/B route."
),
},
],
"pussy licking leg up - back view - 3-4 angle": [
{
"variant_key": "normal_pussy_licking_leg_up_back_three_quarter_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["pussy licking leg up - back view - 3-4 angle/0009.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one leg-up rear-offset oral-contact "
"sample as coverage evidence only. A single image cannot define a stable "
"normal-camera route."
),
},
],
"pussy licking standing woman": [
{
"variant_key": "normal_pussy_licking_standing_woman_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["pussy licking standing woman/13.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one standing-woman oral-contact "
"sample as coverage evidence only. A single image cannot define a stable "
"normal-camera route."
),
},
],
"under desk": [
{
"variant_key": "normal_under_desk_single_reference_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": ["under desk/347.jpg"],
"evidence_notes": (
"Contact-sheet review preserved the one under-desk context sample as "
"coverage evidence only. A single image cannot define a stable "
"normal-camera route."
),
},
],
"pretzel": [
{
"variant_key": "normal_pretzel_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"pretzel/0001.jpg",
"pretzel/0006.jpg",
],
"evidence_notes": (
"Contact-sheet review records the existing two-sample pretzel mixed-camera "
"source pool as needs_samples. The folder is fully represented but too thin "
"for a selected pre-A/B route."
),
},
],
"woman ass exposed": [
{
"variant_key": "normal_display_rear_exposed_body_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"woman ass exposed/40.jpg",
"woman ass exposed/49.jpg",
"woman ass exposed/52.jpg",
"woman ass exposed/58.jpg",
],
"evidence_notes": (
"Contact-sheet review records the existing rear exposed-body source pool "
"with all four folder images. The folder is fully represented but remains "
"too thin and camera-variable for a selected pre-A/B route."
),
},
],
"reverse congress - front view": [
{
"variant_key": "normal_reverse_congress_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"reverse congress - front view/0012.jpg",
"reverse congress - front view/0012_2.jpg",
"reverse congress - front view/0013.jpg",
"reverse congress - front view/61.jpg",
"reverse congress - front view/105.jpg",
"reverse congress - front view/105_2.jpg",
"reverse congress - front view/122.jpg",
"reverse congress - front view/183.jpg",
"reverse congress - front view/189.jpg",
"reverse congress - front view/289.jpg",
"reverse congress - front view/289_2.jpg",
"reverse congress - front view/291.jpg",
"reverse congress - front view/542.jpg",
"reverse congress - front view/542_2.jpg",
"reverse congress - front view/563_2.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed the existing front-facing reverse-congress "
"route as a coherent lifted/front camera family. Tight low crops, "
"side/bed drift, and seated-only outliers remain outside the selected subset."
),
}
],
"boobjob": [
{
"variant_key": "normal_boobjob_front_close_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"boobjob/0017.jpg",
"boobjob/259.jpg",
"boobjob/91.jpg",
],
"evidence_notes": (
"Residual-pool review preserved the existing mixed source-pool anchor. "
"The full folder includes useful front-close material, but side, standing, "
"foreground-heavy, and downward/crop-drifting examples keep it from being "
"one locked prompt-ready camera route."
),
},
{
"variant_key": "normal_boobjob_front_close_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"boobjob/0017.jpg",
"boobjob/0018_2.jpg",
"boobjob/141.jpg",
"boobjob/186.jpg",
"boobjob/218.jpg",
"boobjob/259.jpg",
"boobjob/327.jpg",
"boobjob/588.jpg",
"boobjob/592.jpg",
"boobjob/598.jpg",
"boobjob/615.jpg",
"boobjob/618.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated front-close third-person family "
"where face or upper body remains readable and the contact action stays "
"centered. Side, standing, foreground-heavy, and downward/crop-drifting "
"outliers remain outside this selected subset."
),
},
],
"fingering": [
{
"variant_key": "normal_fingering_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"fingering/0001.jpg",
"fingering/195.jpg",
"fingering/603.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the folder as a mixed source-pool anchor. "
"The images include useful manual-contact examples, but upright, standing, "
"side, and oral-adjacent frames keep the full folder from being one locked "
"prompt-ready route."
),
},
{
"variant_key": "normal_fingering_reclined_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"fingering/0004.jpg",
"fingering/0006.jpg",
"fingering/0013.jpg",
"fingering/0015.jpg",
"fingering/39.jpg",
"fingering/50.jpg",
"fingering/132.jpg",
"fingering/133.jpg",
"fingering/279.jpg",
"fingering/603.jpg",
],
"evidence_notes": (
"Contact-sheet review found a repeated reclined front-view manual-contact "
"family where the camera faces the open-thigh contact plane and face or "
"upper body remains readable. Upright chair, standing, side, and stronger "
"oral-adjacent outliers remain outside this selected subset."
),
},
],
"69": [
{
"variant_key": "normal_sixty_nine_mixed_camera_folder_pool",
"review_bucket": "reject_or_unclear",
"status": "needs_samples",
"selection_date": "2026-07-02",
"reference_images": [
"69/0002.jpg",
"69/196.jpg",
"69/624.jpg",
],
"evidence_notes": (
"Contact-sheet review preserved the folder as a mixed source-pool anchor. "
"The source images include useful mutual oral-contact examples, but "
"side/downward, upright seated, and wider room-context frames keep the "
"full folder from being one locked prompt-ready route."
),
},
{
"variant_key": "normal_sixty_nine_front_close_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"69/0002.jpg",
"69/0008.jpg",
"69/0016.jpg",
"69/19.jpg",
"69/166.jpg",
"69/292.jpg",
"69/624.jpg",
],
"evidence_notes": (
"Contact-sheet review found a repeated front-close third-person family "
"where the upper subject faces camera and the opposed oral-contact pose "
"stays centered. Side/downward, upright seated, and wider room-context "
"outliers remain outside this selected subset."
),
},
],
"breast sucking - side view": [
{
"variant_key": "normal_breast_sucking_side_view",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"breast sucking - side view/0003.jpg",
"breast sucking - side view/0005.jpg",
"breast sucking - side view/0007.jpg",
"breast sucking - side view/0009.jpg",
"breast sucking - side view/0010_2.jpg",
"breast sucking - side view/182.jpg",
"breast sucking - side view/186.jpg",
"breast sucking - side view/218.jpg",
"breast sucking - side view/234.jpg",
"breast sucking - side view/244.jpg",
"breast sucking - side view/257.jpg",
"breast sucking - side view/267.jpg",
"breast sucking - side view/269.jpg",
"breast sucking - side view/356.jpg",
"breast sucking - side view/99.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed a repeated side/near-side mouth-contact "
"family across standing and seated contexts. References were expanded "
"while front-only, hand-touch-only, and weak-contact examples remain outside "
"the selected subset."
),
}
],
"fuck from front standing - side view": [
{
"variant_key": "normal_standing_from_front_side_view",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-06-29",
"reference_images": [
"fuck from front standing - side view/0001.jpg",
"fuck from front standing - side view/252.jpg",
"fuck from front standing - side view/73.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing selected standing from-front side-view "
"subset unchanged. The remaining source images include usable side/near-side "
"frames, but too many drift into suspended/lifted, close-crop, couch, or "
"front-heavy framing for a safe reference expansion."
),
}
],
"fuck from behind standing - woman backside - side view": [
{
"variant_key": "normal_doggy_standing_backside_side_view_folder_pool",
"review_bucket": "side_view",
"status": "needs_samples",
"selection_date": "2026-06-29",
"reference_images": [
"fuck from behind standing - woman backside - side view/0008.jpg",
"fuck from behind standing - woman backside - side view/288.jpg",
"fuck from behind standing - woman backside - side view/68_2.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing folder-pool anchor unchanged. "
"The folder has standing side/rear-lateral material, but the unselected "
"images mix lifted, seated, bed/couch, front-drifting, and portrait-like "
"frames rather than a cleaner prompt-ready split."
),
},
{
"variant_key": "normal_doggy_standing_backside_side_view",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-06-29",
"reference_images": [
"fuck from behind standing - woman backside - side view/0010.jpg",
"fuck from behind standing - woman backside - side view/0012.jpg",
"fuck from behind standing - woman backside - side view/186.jpg",
"fuck from behind standing - woman backside - side view/186_2.jpg",
"fuck from behind standing - woman backside - side view/196.jpg",
"fuck from behind standing - woman backside - side view/257.jpg",
"fuck from behind standing - woman backside - side view/286.jpg",
"fuck from behind standing - woman backside - side view/288.jpg",
"fuck from behind standing - woman backside - side view/343.jpg",
"fuck from behind standing - woman backside - side view/343_2.jpg",
"fuck from behind standing - woman backside - side view/343_3.jpg",
"fuck from behind standing - woman backside - side view/345.jpg",
"fuck from behind standing - woman backside - side view/345_2.jpg",
"fuck from behind standing - woman backside - side view/542.jpg",
"fuck from behind standing - woman backside - side view/68.jpg",
"fuck from behind standing - woman backside - side view/68_2.jpg",
],
"evidence_notes": (
"Residual-pool review kept the existing standing backside side-view route "
"unchanged. It already has a coherent reviewed subset; remaining images "
"do not add a distinct repeated camera family in this pass."
),
},
],
"doggy - back view - 3-4 angle": [
{
"variant_key": "normal_doggy_generic_back_three_quarter",
"review_bucket": "back_three_quarter",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"doggy - back view - 3-4 angle/0002.jpg",
"doggy - back view - 3-4 angle/0002_2.jpg",
"doggy - back view - 3-4 angle/0008_2.jpg",
"doggy - back view - 3-4 angle/0013.jpg",
"doggy - back view - 3-4 angle/0015.jpg",
"doggy - back view - 3-4 angle/0017.jpg",
"doggy - back view - 3-4 angle/0021.jpg",
"doggy - back view - 3-4 angle/132.jpg",
"doggy - back view - 3-4 angle/145.jpg",
"doggy - back view - 3-4 angle/183.jpg",
"doggy - back view - 3-4 angle/185.jpg",
"doggy - back view - 3-4 angle/227.jpg",
"doggy - back view - 3-4 angle/39.jpg",
"doggy - back view - 3-4 angle/41.jpg",
"doggy - back view - 3-4 angle/77.jpg",
],
"evidence_notes": (
"Residual-pool review confirmed the existing generic rear three-quarter "
"route as a coherent back-offset camera family. References were expanded "
"while low/close and laying-drift examples remain outside the selected subset."
),
}
],
"woman solo showing her hass - back view": [
{
"variant_key": "normal_display_rear_body_standing_back_view",
"review_bucket": "back_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"woman solo showing her hass - back view/0002.jpg",
"woman solo showing her hass - back view/0006.jpg",
"woman solo showing her hass - back view/0006_3.jpg",
"woman solo showing her hass - back view/0007_2.jpg",
"woman solo showing her hass - back view/173.jpg",
"woman solo showing her hass - back view/197.jpg",
"woman solo showing her hass - back view/230.jpg",
"woman solo showing her hass - back view/235.jpg",
"woman solo showing her hass - back view/287.jpg",
"woman solo showing her hass - back view/293.jpg",
"woman solo showing her hass - back view/300.jpg",
"woman solo showing her hass - back view/321.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated upright standing rear-body display "
"family. Seated, kneeling, side-leaning, and close-crop rear examples remain "
"in the broad source pool or existing rear-body route."
),
}
],
"couple kissing": [
{
"variant_key": "normal_couple_kissing_upright_side_profile",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"couple kissing/0001_2.jpg",
"couple kissing/0003.jpg",
"couple kissing/0005_2.jpg",
"couple kissing/16.jpg",
"couple kissing/159.jpg",
"couple kissing/247.jpg",
"couple kissing/247_2.jpg",
"couple kissing/253.jpg",
"couple kissing/253_2.jpg",
"couple kissing/322.jpg",
"couple kissing/354.jpg",
"couple kissing/379.jpg",
],
"evidence_notes": (
"Paged review found a repeated upright side-profile kissing family. "
"Seated, bed, close-crop, and front-facing examples remain in the mixed source pool."
),
}
],
"anal random": [
{
"variant_key": "normal_anal_random_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"anal random/0005.jpg",
"anal random/0006.jpg",
"anal random/0015.jpg",
"anal random/160_3.jpg",
"anal random/188.jpg",
"anal random/347_3.jpg",
"anal random/347_10.jpg",
"anal random/347_11.jpg",
"anal random/366.jpg",
"anal random/366_3.jpg",
"anal random/366_4.jpg",
],
"evidence_notes": (
"Paged review found a repeated front-view anal camera family. "
"Side, rear, standing, and overhead-like frames remain in the mixed source pool."
),
},
{
"variant_key": "normal_anal_random_back_side_offset_view",
"review_bucket": "back_three_quarter",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"anal random/0003.jpg",
"anal random/0003_3.jpg",
"anal random/0010.jpg",
"anal random/0013.jpg",
"anal random/119.jpg",
"anal random/119_2.jpg",
"anal random/119_3.jpg",
"anal random/124_2.jpg",
"anal random/124_8.jpg",
"anal random/347_4.jpg",
"anal random/347_5.jpg",
"anal random/347_8.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated rear/side-offset anal camera family "
"with third-person framing. Front-view, standing, overhead-like, and tight "
"close-crop frames remain in the mixed source pool."
),
}
],
"breasts exposed": [
{
"variant_key": "normal_display_breasts_exposed_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"breasts exposed/0001_3.jpg",
"breasts exposed/0006.jpg",
"breasts exposed/0008_2.jpg",
"breasts exposed/0011_2.jpg",
"breasts exposed/141.jpg",
"breasts exposed/181.jpg",
"breasts exposed/263.jpg",
"breasts exposed/302.jpg",
"breasts exposed/327.jpg",
"breasts exposed/346.jpg",
"breasts exposed/594_2.jpg",
"breasts exposed/624.jpg",
],
"evidence_notes": (
"Paged review found a repeated camera-facing full-body display family "
"inside the broad mixed source folder. Seated, side-offset, and tight "
"upper-body crops stay separate from this selected subset."
),
},
{
"variant_key": "normal_display_breasts_exposed_side_offset_view",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"breasts exposed/83.jpg",
"breasts exposed/196.jpg",
"breasts exposed/235.jpg",
"breasts exposed/267.jpg",
"breasts exposed/271.jpg",
"breasts exposed/305.jpg",
"breasts exposed/353.jpg",
"breasts exposed/373.jpg",
"breasts exposed/590.jpg",
"breasts exposed/590_2.jpg",
],
"evidence_notes": (
"Paged review found a repeated side-offset display family with lateral "
"torso orientation and normal third-person framing."
),
},
{
"variant_key": "normal_display_breasts_exposed_standing_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"breasts exposed/0002_2.jpg",
"breasts exposed/0003.jpg",
"breasts exposed/49.jpg",
"breasts exposed/138.jpg",
"breasts exposed/155.jpg",
"breasts exposed/169.jpg",
"breasts exposed/190.jpg",
"breasts exposed/230.jpg",
"breasts exposed/242.jpg",
"breasts exposed/302_2.jpg",
"breasts exposed/332.jpg",
"breasts exposed/368.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated upright standing front-view display "
"family. Seated, kneeling, side-offset, and tight crop examples remain in the "
"mixed source pool."
),
},
{
"variant_key": "normal_display_breasts_exposed_seated_kneeling_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"breasts exposed/0001_2.jpg",
"breasts exposed/0007_2.jpg",
"breasts exposed/101_2.jpg",
"breasts exposed/154.jpg",
"breasts exposed/165.jpg",
"breasts exposed/173.jpg",
"breasts exposed/259.jpg",
"breasts exposed/285.jpg",
"breasts exposed/293.jpg",
"breasts exposed/322.jpg",
"breasts exposed/328.jpg",
"breasts exposed/523.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated seated or kneeling front-view display "
"family. Standing, side-offset, couple/action, and tight crop examples remain "
"outside this selected subset."
),
},
],
"pussy spread": [
{
"variant_key": "normal_display_front_open_leg_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"pussy spread/0002_3.jpg",
"pussy spread/0003_4.jpg",
"pussy spread/0012.jpg",
"pussy spread/0015.jpg",
"pussy spread/0017.jpg",
"pussy spread/80.jpg",
"pussy spread/130.jpg",
"pussy spread/153.jpg",
"pussy spread/296.jpg",
"pussy spread/297.jpg",
"pussy spread/328.jpg",
"pussy spread/367.jpg",
],
"evidence_notes": (
"Paged review found a repeated camera-facing front-view display family "
"with enough body and room context to stay separate from tight low-close crops."
),
},
{
"variant_key": "normal_display_front_open_leg_low_close",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"pussy spread/1.jpg",
"pussy spread/1_2.jpg",
"pussy spread/0003.jpg",
"pussy spread/0003_2.jpg",
"pussy spread/0003_3.jpg",
"pussy spread/0010_2.jpg",
"pussy spread/0016.jpg",
"pussy spread/0016_2.jpg",
"pussy spread/41.jpg",
"pussy spread/201.jpg",
"pussy spread/217_2.jpg",
"pussy spread/223.jpg",
],
"evidence_notes": (
"Paged review found a repeated low-close front-view display family "
"where the foreground body plane dominates the composition."
),
},
{
"variant_key": "normal_display_front_open_leg_side_offset_view",
"review_bucket": "side_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"pussy spread/0007.jpg",
"pussy spread/0008.jpg",
"pussy spread/73.jpg",
"pussy spread/121.jpg",
"pussy spread/134.jpg",
"pussy spread/154_3.jpg",
"pussy spread/163.jpg",
"pussy spread/175.jpg",
"pussy spread/256.jpg",
"pussy spread/308.jpg",
"pussy spread/311.jpg",
"pussy spread/368.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated side-offset seated or reclined "
"open-leg display family. Straight front, low-close, and one-off crop "
"variants remain in the mixed source pool."
),
},
],
"reverse cowgirl": [
{
"variant_key": "normal_reverse_cowgirl_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"reverse cowgirl/0001.jpg",
"reverse cowgirl/0004.jpg",
"reverse cowgirl/0005_4.jpg",
"reverse cowgirl/0014.jpg",
"reverse cowgirl/79.jpg",
"reverse cowgirl/82.jpg",
"reverse cowgirl/145.jpg",
"reverse cowgirl/260.jpg",
"reverse cowgirl/288.jpg",
"reverse cowgirl/319.jpg",
"reverse cowgirl/527.jpg",
"reverse cowgirl/608.jpg",
],
"evidence_notes": (
"Paged review found a repeated front-facing reverse-cowgirl camera family "
"across the large mixed source folder. Rear and side labels remain covered "
"by the existing dedicated source folders until cleaner selected subsets are chosen."
),
},
{
"variant_key": "normal_reverse_cowgirl_front_three_quarter",
"review_bucket": "front_three_quarter",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"reverse cowgirl/153.jpg",
"reverse cowgirl/153_2.jpg",
"reverse cowgirl/239_2.jpg",
"reverse cowgirl/239_3.jpg",
"reverse cowgirl/241.jpg",
"reverse cowgirl/241_2.jpg",
"reverse cowgirl/241_3.jpg",
"reverse cowgirl/257.jpg",
"reverse cowgirl/287.jpg",
"reverse cowgirl/323_2.jpg",
"reverse cowgirl/609.jpg",
"reverse cowgirl/609_2.jpg",
],
"evidence_notes": (
"Paged review found a repeated front-offset reverse-cowgirl camera family "
"where the woman's body turns partly toward camera while the partner remains "
"visible below or behind her in third person."
),
},
{
"variant_key": "normal_reverse_cowgirl_low_close_front_view",
"review_bucket": "front_view",
"status": "pre_ab_candidate",
"selection_date": "2026-07-02",
"reference_images": [
"reverse cowgirl/0009.jpg",
"reverse cowgirl/0009_2.jpg",
"reverse cowgirl/0009_3.jpg",
"reverse cowgirl/0015.jpg",
"reverse cowgirl/0015_2.jpg",
"reverse cowgirl/0016_2.jpg",
"reverse cowgirl/170.jpg",
"reverse cowgirl/261.jpg",
"reverse cowgirl/332.jpg",
"reverse cowgirl/332_2.jpg",
"reverse cowgirl/551.jpg",
"reverse cowgirl/618.jpg",
],
"evidence_notes": (
"Residual-pool review found a repeated low-close front-view reverse-cowgirl "
"family where the foreground contact plane dominates while the subject remains "
"visible in third-person framing. Wider room-context front views stay in the "
"existing front-view subset."
),
}
]
}
ACCEPTANCE_GATES: tuple[str, ...] = (
"camera_geometry",
"pose_ownership",
"workspace_continuity",
"clothing_visibility",
"subject_identity",
"body_proportion_control",
"prompt_noise",
"atlas_reference_match",
)
GATE_DESCRIPTIONS: dict[str, str] = {
"camera_geometry": "Camera angle, elevation, side/front/back orientation, and framing match the atlas family.",
"pose_ownership": "The visible actors own the pose correctly, with no POV body cues leaking into normal-camera framing.",
"workspace_continuity": "Workspace/lounge details support the camera angle instead of fighting the pose.",
"clothing_visibility": "Clothing cues stay attached to the intended visible subject and only describe garments visible in this pose.",
"subject_identity": "The same woman identity, face, hair, eyes, and body type remain stable across fixed-seed variants.",
"body_proportion_control": "Penis/body proportions and limb lengths stay plausible for the selected atlas framing.",
"prompt_noise": "Prompt text uses direct visual cues, with option, negative, and instruction-like wording removed from positive text.",
"atlas_reference_match": "The generated frame can be matched back to the selected atlas references for this variant.",
}
PROMPT_NOISE_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
("option_word", re.compile(r"\b(?:may|optionally|either|or)\b", flags=re.IGNORECASE)),
("negative_word", re.compile(r"\b(?:without|never|no|not)\b", flags=re.IGNORECASE)),
("contrast_wording", re.compile(r"\brather than\b", flags=re.IGNORECASE)),
("instruction_wording", re.compile(r"\b(?:avoid|exclude|prevent|forbid|should|must)\b", flags=re.IGNORECASE)),
)
def _load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def load_atlas() -> dict[str, Any]:
return _load_json(CATALOG_DIR / "normal_camera_atlas.json")
def load_variants() -> dict[str, Any]:
return _load_json(CATALOG_DIR / "normal_camera_variants.json")
def _limited(items: list[dict[str, Any]], limit: int | None) -> list[dict[str, Any]]:
if limit is None:
return items
if limit < 0:
raise ValueError("limit must be non-negative")
return items[:limit]
def _natural_sort_key(text: str) -> list[int | str]:
return [int(part) if part.isdigit() else part.lower() for part in re.split(r"(\d+)", text)]
def _safe_artifact_stem(text: str) -> str:
stem = re.sub(r"[^a-z0-9]+", "_", text.lower()).strip("_")
return stem or "folder"
def _folder_alias(folder_name: str) -> dict[str, Any]:
alias = SOURCE_FOLDER_ALIASES.get(folder_name) or {}
canonical_folder = str(alias.get("canonical_folder") or folder_name)
return {
"exact_source_folder": folder_name,
"canonical_folder": canonical_folder,
"canonical_key": _safe_artifact_stem(canonical_folder),
"alias_applied": bool(alias),
"alias_reason": str(alias.get("alias_reason") or ""),
}
def _atlas_folder_rows() -> list[dict[str, Any]]:
return list(load_atlas().get("folders") or [])
def _atlas_folder_row(folder_name: str) -> dict[str, Any]:
for folder in _atlas_folder_rows():
if folder.get("folder") == folder_name:
return folder
raise KeyError(f"unknown normal-camera atlas folder: {folder_name}")
def _variant_reference_map() -> dict[str, set[str]]:
refs_by_folder: dict[str, set[str]] = {}
for variant in load_variants().get("variants") or []:
for ref in _reference_images(variant):
folder = ref.rsplit("/", 1)[0]
refs_by_folder.setdefault(folder, set()).add(ref)
return refs_by_folder
def _covered_variant_folders() -> set[str]:
covered: set[str] = set()
for variant in load_variants().get("variants") or []:
for folder in variant.get("atlas_folders") or []:
folder_text = str(folder)
if folder_text.strip():
covered.add(folder_text)
return covered
def _folder_image_refs(folder_name: str) -> list[str]:
atlas_root = Path(str(load_atlas().get("atlas_root") or ""))
source_dir = atlas_root / folder_name
if source_dir.is_dir():
image_names = sorted(
(path.name for path in source_dir.iterdir() if path.is_file() and path.suffix.lower() == ".jpg"),
key=_natural_sort_key,
)
return [f"{folder_name}/{name}" for name in image_names]
return [str(ref) for ref in _atlas_folder_row(folder_name).get("reference_images") or [] if str(ref).strip()]
def _variant_rows(status: str | None = None, limit: int | None = None) -> list[dict[str, Any]]:
variants = load_variants().get("variants") or []
rows = [variant for variant in variants if not status or variant.get("status") == status]
return _limited(rows, limit)
def _reference_images(variant: dict[str, Any]) -> list[str]:
return [str(ref) for ref in variant.get("reference_images") or [] if str(ref).strip()]
def _source_prompt_cues(variant: dict[str, Any]) -> list[str]:
return [str(cue).strip() for cue in variant.get("prompt_cues") or [] if str(cue).strip()]
def _avoid_cues(variant: dict[str, Any]) -> list[str]:
return [str(cue).strip() for cue in variant.get("avoid_cues") or [] if str(cue).strip()]
def _prompt_noise(cue: str) -> list[str]:
return [name for name, pattern in PROMPT_NOISE_PATTERNS if pattern.search(cue)]
def _prompt_cue_review(variant: dict[str, Any]) -> dict[str, Any]:
positive_cues: list[str] = []
blocked_cues: list[dict[str, Any]] = []
for cue in _source_prompt_cues(variant):
issues = _prompt_noise(cue)
if issues:
blocked_cues.append({"cue": cue, "issues": issues})
else:
positive_cues.append(cue)
state = "prompt_ready" if positive_cues and not blocked_cues else "needs_prompt_cleanup"
return {
"state": state,
"positive_prompt_cues": positive_cues,
"blocked_prompt_cues": blocked_cues,
"prompt_text": ". ".join(positive_cues) + ("." if positive_cues else ""),
}
def _unused_pool_suggested_action(folder: dict[str, Any], covered_by_variants: bool) -> str:
if not covered_by_variants:
return "review_contact_sheet"
if folder.get("camera_view") == "mixed_or_unspecified" or folder.get("catalog_status") == "action_reference_only":
return "split_or_expand_selected_reference_subvariants"
return "mine_remaining_reference_images"
def build_unused_pool_backlog(limit: int | None = None) -> dict[str, Any]:
refs_by_folder = _variant_reference_map()
covered_folders = _covered_variant_folders()
rows: list[dict[str, Any]] = []
for folder in _atlas_folder_rows():
image_count = int(folder.get("image_count") or 0)
if image_count <= 0:
continue
folder_name = str(folder.get("folder") or "")
selected_reference_count = len(refs_by_folder.get(folder_name, set()))
remaining_image_count = max(image_count - selected_reference_count, 0)
if remaining_image_count <= 0:
continue
covered_by_variants = folder_name in covered_folders
rows.append(
{
"folder": folder_name,
"folder_alias": _folder_alias(folder_name),
"image_count": image_count,
"selected_reference_count": selected_reference_count,
"remaining_image_count": remaining_image_count,
"covered_by_variants": covered_by_variants,
"camera_view": folder.get("camera_view"),
"action_family": folder.get("action_family"),
"catalog_status": folder.get("catalog_status"),
"suggested_action": _unused_pool_suggested_action(folder, covered_by_variants),
}
)
rows.sort(key=lambda row: (-int(row["remaining_image_count"]), str(row["folder"])))
selected = _limited(rows, limit)
return {
"schema": UNUSED_POOL_BACKLOG_SCHEMA,
"no_generation": True,
"atlas_root": load_atlas().get("atlas_root"),
"source_inventory": "categories/normal_camera_atlas.json",
"source_variant_catalog": "categories/normal_camera_variants.json",
"selection": "non-empty folders with unselected images, sorted by remaining image count",
"available_folder_count": len(rows),
"selected_count": len(selected),
"folders": selected,
}
def _selected_review_items(folder_name: str) -> dict[str, dict[str, Any]]:
items: dict[str, dict[str, Any]] = {}
for selected in REVIEW_SELECTED_SUBVARIANTS.get(folder_name, []):
for ref in selected.get("reference_images") or []:
ref_text = str(ref)
if not ref_text.strip():
continue
items[ref_text] = {
"reference_image": ref_text,
"review_decision": "selected_reference",
"review_bucket": selected.get("review_bucket"),
"variant_key": selected.get("variant_key"),
"selection_date": selected.get("selection_date"),
"review_notes": selected.get("evidence_notes") or "",
}
return items
def _review_items(folder_name: str, image_refs: list[str]) -> list[dict[str, Any]]:
selected_by_ref = _selected_review_items(folder_name)
items: list[dict[str, Any]] = []
for index, ref in enumerate(image_refs, start=1):
selected = selected_by_ref.get(ref)
if selected:
item = dict(selected)
item["index"] = index
else:
item = {
"index": index,
"reference_image": ref,
"review_decision": "residual_unassigned",
"review_bucket": "unassigned",
"variant_key": None,
"selection_date": None,
"review_notes": "",
"exclusion_reason": "outside_selected_subvariants_or_pending_manual_bucket",
}
items.append(item)
return items
def _contact_sheet_pages(
image_refs: list[str],
page_size: int,
review_items_by_ref: dict[str, dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
pages: list[dict[str, Any]] = []
for page_index, start in enumerate(range(0, len(image_refs), page_size), start=1):
page_refs = image_refs[start : start + page_size]
pages.append(
{
"page": page_index,
"html_anchor": f"page-{page_index:02d}",
"start_index": start + 1,
"end_index": start + len(page_refs),
"image_count": len(page_refs),
"images": [
{
"index": start + offset + 1,
"reference_image": ref,
"review_bucket": (review_items_by_ref or {}).get(ref, {}).get("review_bucket") or "unassigned",
"review_decision": (review_items_by_ref or {}).get(ref, {}).get("review_decision")
or "residual_unassigned",
"variant_key": (review_items_by_ref or {}).get(ref, {}).get("variant_key"),
"notes": (review_items_by_ref or {}).get(ref, {}).get("review_notes") or "",
}
for offset, ref in enumerate(page_refs)
],
}
)
return pages
def build_review_manifest(folder_name: str, page_size: int = 40) -> dict[str, Any]:
if page_size <= 0:
raise ValueError("page_size must be positive")
folder = _atlas_folder_row(folder_name)
image_refs = _folder_image_refs(folder_name)
review_items = _review_items(folder_name, image_refs)
review_items_by_ref = {str(item["reference_image"]): item for item in review_items}
return {
"schema": REVIEW_MANIFEST_SCHEMA,
"no_generation": True,
"atlas_root": load_atlas().get("atlas_root"),
"folder": folder_name,
"folder_alias": _folder_alias(folder_name),
"image_count": len(image_refs),
"action_family": folder.get("action_family"),
"source_camera_view": folder.get("camera_view"),
"source_catalog_status": folder.get("catalog_status"),
"review_state": "contact_sheet_ready",
"methodology": [
"Treat this folder as a cue-expansion pool, not automatic prompt truth.",
"Cluster only repeated non-POV camera/pose families.",
"Draft positive-only visual prompt cues from selected atlas references.",
"Keep weak, mixed, or one-off groups as needs_samples.",
],
"review_bucket_values": REVIEW_BUCKET_VALUES,
"selected_subvariants": REVIEW_SELECTED_SUBVARIANTS.get(folder_name, []),
"review_items": review_items,
"contact_sheet_pages": _contact_sheet_pages(image_refs, page_size, review_items_by_ref),
}
def build_contact_sheet_html(folder_name: str, page_size: int = 40) -> str:
manifest = build_review_manifest(folder_name, page_size)
atlas_root = Path(str(manifest.get("atlas_root") or ""))
title = f"Normal Camera Review Contact Sheet: {folder_name}"
review_items_by_ref = {str(item.get("reference_image")): item for item in manifest.get("review_items") or []}
alias = manifest.get("folder_alias") or {}
lines = [
"<!doctype html>",
'<html lang="en">',
"<head>",
'<meta charset="utf-8">',
f"<title>{html.escape(title)}</title>",
"<style>",
"body{font-family:Arial,sans-serif;margin:24px;background:#f6f6f3;color:#1f2328}",
"h1{font-size:22px;margin:0 0 8px}",
"h2{font-size:18px;margin:28px 0 12px}",
".meta{margin:0 0 16px;color:#4b5563}",
".buckets{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px;margin:16px 0}",
".bucket{background:#fff;border:1px solid #d0d7de;border-radius:6px;padding:8px}",
".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px}",
"figure{margin:0;background:#fff;border:1px solid #d0d7de;border-radius:6px;overflow:hidden}",
"img{display:block;width:100%;height:150px;object-fit:cover;background:#ddd}",
"figcaption{font-size:12px;line-height:1.35;padding:6px;word-break:break-word}",
".bucket-slot{display:block;margin-top:4px;color:#6b7280}",
"</style>",
"</head>",
"<body>",
f"<h1>{html.escape(title)}</h1>",
(
f'<p class="meta">No-generation contact sheet. {manifest["image_count"]} JPGs from '
f'<code>{html.escape(folder_name)}</code>. Review buckets are placeholders until visual review assigns them.</p>'
),
(
f'<p class="meta">Canonical folder label: <code>{html.escape(str(alias.get("canonical_folder") or folder_name))}</code>.</p>'
),
'<div class="buckets">',
]
for bucket, description in REVIEW_BUCKET_VALUES.items():
lines.append(f'<div class="bucket"><strong>{html.escape(bucket)}</strong><br>{html.escape(description)}</div>')
lines.append("</div>")
for page in manifest["contact_sheet_pages"]:
lines.append(f'<h2 id="{html.escape(page["html_anchor"])}">Page {page["page"]}: {page["start_index"]}-{page["end_index"]}</h2>')
lines.append('<div class="grid">')
for image in page["images"]:
ref = str(image["reference_image"])
src = (atlas_root / ref).as_uri() if atlas_root.is_absolute() else ref
review_item = review_items_by_ref.get(ref) or {}
review_bucket = str(review_item.get("review_bucket") or "unassigned")
review_decision = str(review_item.get("review_decision") or "residual_unassigned")
selected_variant = str(review_item.get("variant_key") or "")
bucket_label = review_bucket
variant_label = f" / {selected_variant}" if selected_variant else ""
lines.extend(
[
(
f'<figure data-review-bucket="{html.escape(review_bucket)}" '
f'data-review-decision="{html.escape(review_decision)}" '
f'data-selected-variant="{html.escape(selected_variant)}" '
f'data-reference-image="{html.escape(ref)}">'
),
f'<img src="{html.escape(src)}" alt="{html.escape(ref)}">',
(
f'<figcaption><strong>{image["index"]:03d}</strong> '
f'{html.escape(ref)}<span class="bucket-slot">bucket: {html.escape(bucket_label)}'
f'{html.escape(variant_label)}</span></figcaption>'
),
"</figure>",
]
)
lines.append("</div>")
lines.extend(["</body>", "</html>", ""])
return "\n".join(lines)
def _review_index_rows() -> list[dict[str, Any]]:
rows: list[dict[str, Any]] = []
for folder_name in DEFAULT_REVIEW_FOLDERS:
manifest = build_review_manifest(folder_name)
stem = _safe_artifact_stem(folder_name)
decision_counts: dict[str, int] = {}
for item in manifest.get("review_items") or []:
decision = str(item.get("review_decision") or "unknown")
decision_counts[decision] = decision_counts.get(decision, 0) + 1
rows.append(
{
"folder": folder_name,
"folder_alias": manifest.get("folder_alias"),
"image_count": manifest.get("image_count"),
"action_family": manifest.get("action_family"),
"source_camera_view": manifest.get("source_camera_view"),
"source_catalog_status": manifest.get("source_catalog_status"),
"selected_subvariant_count": len(manifest.get("selected_subvariants") or []),
"selected_reference_count": int(decision_counts.get("selected_reference") or 0),
"residual_unassigned_count": int(decision_counts.get("residual_unassigned") or 0),
"review_decision_counts": decision_counts,
"manifest_path": f"{stem}_review_manifest.json",
"contact_sheet_path": f"{stem}_contact_sheet.html",
}
)
return rows
def _markdown_cell(value: Any) -> str:
return str(value if value is not None else "").replace("|", "\\|")
def build_review_index_markdown() -> str:
lines = [
"# Normal Camera Review Index",
"",
"No-generation index for local review manifests and contact sheets.",
"",
"| Source folder | Canonical label | Images | Selected refs | Residual | Manifest | Contact sheet |",
"| --- | --- | ---: | ---: | ---: | --- | --- |",
]
for row in _review_index_rows():
alias = row.get("folder_alias") or {}
lines.append(
"| "
+ " | ".join(
[
_markdown_cell(row.get("folder")),
_markdown_cell(alias.get("canonical_folder")),
_markdown_cell(row.get("image_count")),
_markdown_cell(row.get("selected_reference_count")),
_markdown_cell(row.get("residual_unassigned_count")),
f"[manifest]({_markdown_cell(row.get('manifest_path'))})",
f"[contact sheet]({_markdown_cell(row.get('contact_sheet_path'))})",
]
)
+ " |"
)
lines.append("")
return "\n".join(lines)
def build_review_index_html() -> str:
lines = [
"<!doctype html>",
'<html lang="en">',
"<head>",
'<meta charset="utf-8">',
"<title>Normal Camera Review Index</title>",
"<style>",
"body{font-family:Arial,sans-serif;margin:24px;background:#f6f6f3;color:#1f2328}",
"h1{font-size:22px;margin:0 0 8px}",
".meta{margin:0 0 16px;color:#4b5563}",
"table{border-collapse:collapse;width:100%;background:#fff}",
"th,td{border:1px solid #d0d7de;padding:8px;text-align:left;font-size:13px;vertical-align:top}",
"th{background:#eef0f3}",
"td.num{text-align:right}",
"code{font-size:12px}",
"</style>",
"</head>",
"<body>",
"<h1>Normal Camera Review Index</h1>",
'<p class="meta">No-generation index for local review manifests and contact sheets.</p>',
"<table>",
"<thead><tr><th>Source folder</th><th>Canonical label</th><th>Images</th><th>Selected refs</th><th>Residual</th><th>Manifest</th><th>Contact sheet</th></tr></thead>",
"<tbody>",
]
for row in _review_index_rows():
alias = row.get("folder_alias") or {}
manifest_path = str(row.get("manifest_path") or "")
contact_sheet_path = str(row.get("contact_sheet_path") or "")
lines.append(
"<tr>"
f"<td><code>{html.escape(str(row.get('folder') or ''))}</code></td>"
f"<td><code>{html.escape(str(alias.get('canonical_folder') or ''))}</code></td>"
f"<td class=\"num\">{html.escape(str(row.get('image_count') or 0))}</td>"
f"<td class=\"num\">{html.escape(str(row.get('selected_reference_count') or 0))}</td>"
f"<td class=\"num\">{html.escape(str(row.get('residual_unassigned_count') or 0))}</td>"
f'<td><a href="{html.escape(manifest_path)}">manifest</a></td>'
f'<td><a href="{html.escape(contact_sheet_path)}">contact sheet</a></td>'
"</tr>"
)
lines.extend(["</tbody>", "</table>", "</body>", "</html>", ""])
return "\n".join(lines)
def build_needs_samples_acquisition(limit: int | None = None) -> dict[str, Any]:
rows: list[dict[str, Any]] = []
for variant in _variant_rows("needs_samples"):
reference_count = len(_reference_images(variant))
missing_reference_count = max(NEEDS_SAMPLES_TARGET_REFERENCE_COUNT - reference_count, 0)
rows.append(
{
"variant_key": variant.get("key"),
"family": variant.get("family"),
"action_family": variant.get("action_family"),
"camera_view": variant.get("camera_view"),
"atlas_folders": list(variant.get("atlas_folders") or []),
"folder_aliases": [_folder_alias(str(folder)) for folder in variant.get("atlas_folders") or []],
"reference_count": reference_count,
"target_reference_count": NEEDS_SAMPLES_TARGET_REFERENCE_COUNT,
"missing_reference_count": missing_reference_count,
"canonical_geometry": variant.get("canonical_geometry"),
"suggested_action": "collect_or_mine_repeatable_non_pov_references",
"review_notes": variant.get("pre_ab_notes") or (variant.get("visual_review") or {}).get("notes") or "",
}
)
rows.sort(
key=lambda row: (
-int(row["missing_reference_count"]),
str(row.get("action_family") or ""),
str(row.get("variant_key") or ""),
)
)
for index, row in enumerate(rows, start=1):
row["acquisition_rank"] = index
selected = _limited(rows, limit)
return {
"schema": NEEDS_SAMPLES_ACQUISITION_SCHEMA,
"no_generation": True,
"atlas_root": load_variants().get("atlas_root"),
"source_variant_catalog": "categories/normal_camera_variants.json",
"selection": "needs_samples variants sorted by missing reference count, action family, and variant key",
"target_reference_count": NEEDS_SAMPLES_TARGET_REFERENCE_COUNT,
"available_variant_count": len(rows),
"selected_count": len(selected),
"variants": selected,
}
def build_needs_samples_acquisition_markdown(limit: int | None = None) -> str:
acquisition = build_needs_samples_acquisition(limit)
lines = [
"# Normal Camera Needs-Samples Acquisition",
"",
"No-generation acquisition list for variants that are not ready for fixed-seed pre-A/B testing.",
"",
f"Target reference count per promoted selected-reference family: {NEEDS_SAMPLES_TARGET_REFERENCE_COUNT}.",
"",
"| Rank | Variant | Action | Camera | Refs | Missing | Source folders |",
"| ---: | --- | --- | --- | ---: | ---: | --- |",
]
for row in acquisition.get("variants") or []:
folders = ", ".join(str(folder) for folder in row.get("atlas_folders") or [])
lines.append(
"| "
+ " | ".join(
[
_markdown_cell(row.get("acquisition_rank")),
_markdown_cell(row.get("variant_key")),
_markdown_cell(row.get("action_family")),
_markdown_cell(row.get("camera_view")),
_markdown_cell(row.get("reference_count")),
_markdown_cell(row.get("missing_reference_count")),
_markdown_cell(folders),
]
)
+ " |"
)
lines.append("")
return "\n".join(lines)
def _candidate_base(variant: dict[str, Any], rank: int) -> dict[str, Any]:
hook = variant.get("generator_hook") or {}
return {
"priority_rank": rank,
"key": variant.get("key"),
"family": variant.get("family"),
"action_family": variant.get("action_family"),
"camera_view": variant.get("camera_view"),
"status": variant.get("status"),
"atlas_folders": list(variant.get("atlas_folders") or []),
"reference_images": _reference_images(variant),
"canonical_geometry": variant.get("canonical_geometry"),
"acceptance_gates": list(ACCEPTANCE_GATES),
"priority_reason": (
"Catalog status is pre_ab_candidate with representative references; "
"catalog order is preserved until fixed-seed evidence exists."
),
"generator_hook_notes": hook.get("notes"),
}
def build_priority_plan(limit: int | None = None) -> dict[str, Any]:
variants_catalog = load_variants()
selected = _variant_rows("pre_ab_candidate", limit)
all_pre_ab = _variant_rows("pre_ab_candidate")
status_counts = (variants_catalog.get("inventory") or {}).get("status_counts") or {}
return {
"schema": PRIORITY_PLAN_SCHEMA,
"no_generation": True,
"atlas_root": variants_catalog.get("atlas_root"),
"source_variant_catalog": "categories/normal_camera_variants.json",
"available_pre_ab_count": len(all_pre_ab),
"selected_count": len(selected),
"deferred_counts": {
"needs_samples": int(status_counts.get("needs_samples") or 0),
"hold": int(status_counts.get("hold") or 0),
},
"acceptance_gate_descriptions": GATE_DESCRIPTIONS,
"candidates": [_candidate_base(variant, index) for index, variant in enumerate(selected, start=1)],
}
def build_prompt_cue_batch(limit: int | None = None) -> dict[str, Any]:
selected = _variant_rows("pre_ab_candidate", limit)
items: list[dict[str, Any]] = []
for index, variant in enumerate(selected, start=1):
cue_review = _prompt_cue_review(variant)
items.append(
{
"batch_rank": index,
"variant_key": variant.get("key"),
"family": variant.get("family"),
"camera_view": variant.get("camera_view"),
"atlas_folders": list(variant.get("atlas_folders") or []),
"reference_images": _reference_images(variant),
"canonical_geometry": variant.get("canonical_geometry"),
"source_prompt_cues": _source_prompt_cues(variant),
"review_only_avoid_cues": _avoid_cues(variant),
"state": cue_review["state"],
"positive_prompt_cues": cue_review["positive_prompt_cues"],
"blocked_prompt_cues": cue_review["blocked_prompt_cues"],
"prompt_text": cue_review["prompt_text"],
"prompt_order": [
"subject_identity",
"scene_anchor",
"camera_pose_cues",
"pose-visible_clothing_cues",
"expression_eye_cues",
],
"later_test_seed_slots": {
"generator_seed": None,
"sampling_seed": None,
"fixed_subject_seed": None,
},
}
)
return {
"schema": PROMPT_CUE_BATCH_SCHEMA,
"no_generation": True,
"atlas_root": load_variants().get("atlas_root"),
"selection": "pre_ab_candidate variants in catalog order",
"items": items,
}
def build_score_sheet(limit: int | None = None) -> dict[str, Any]:
selected = _variant_rows("pre_ab_candidate", limit)
rows = []
for index, variant in enumerate(selected, start=1):
rows.append(
{
"batch_rank": index,
"variant_key": variant.get("key"),
"family": variant.get("family"),
"camera_view": variant.get("camera_view"),
"atlas_folders": list(variant.get("atlas_folders") or []),
"reference_images": _reference_images(variant),
"test_prompt_id": None,
"image_path": None,
"generator_seed": None,
"sampling_seed": None,
"scores": {gate: None for gate in ACCEPTANCE_GATES},
"notes": "",
"decision": None,
}
)
return {
"schema": SCORE_SHEET_SCHEMA,
"no_generation": True,
"atlas_root": load_variants().get("atlas_root"),
"acceptance_gates": list(ACCEPTANCE_GATES),
"acceptance_gate_descriptions": GATE_DESCRIPTIONS,
"score_scale": {
"0": "fails the gate",
"1": "partial or unstable",
"2": "usable but needs wording refinement",
"3": "strong enough to preserve as candidate wording",
},
"rows": rows,
}
def build_acceptance_gates_markdown() -> str:
lines = [
"# Normal Camera Pre-A/B Acceptance Gates",
"",
"No-generation gate sheet for later fixed-seed generation review. This file does not record generated evidence.",
"",
]
for gate in ACCEPTANCE_GATES:
lines.append(f"- `{gate}`: {GATE_DESCRIPTIONS[gate]}")
lines.extend(
[
"",
"Prompt-ready cues must stay positive and direct. Cues with option words, negative wording, or instruction-like cleanup text remain review-only until manually rewritten from atlas evidence.",
"",
]
)
return "\n".join(lines)
def write_artifacts(output_dir: Path = DEFAULT_OUTPUT_DIR, limit: int | None = None, indent: int = 2) -> dict[str, Path]:
output_dir.mkdir(parents=True, exist_ok=True)
review_dir = output_dir / "review"
artifacts: dict[str, tuple[Path, Any]] = {
"priority_plan": (output_dir / "normal_camera_priority_plan.json", build_priority_plan(limit)),
"prompt_cue_batch": (output_dir / "normal_camera_prompt_cue_batch.json", build_prompt_cue_batch(limit)),
"score_sheet": (output_dir / "normal_camera_score_sheet.json", build_score_sheet(limit)),
"acceptance_gates": (output_dir / "normal_camera_acceptance_gates.md", build_acceptance_gates_markdown()),
"unused_pool_backlog": (output_dir / "normal_camera_unused_pool_backlog.json", build_unused_pool_backlog(limit)),
"needs_samples_acquisition": (
output_dir / "normal_camera_needs_samples_acquisition.json",
build_needs_samples_acquisition(limit),
),
"needs_samples_acquisition_md": (
output_dir / "normal_camera_needs_samples_acquisition.md",
build_needs_samples_acquisition_markdown(limit),
),
}
for folder_name in DEFAULT_REVIEW_FOLDERS:
stem = _safe_artifact_stem(folder_name)
artifacts[f"{stem}_review_manifest"] = (
review_dir / f"{stem}_review_manifest.json",
build_review_manifest(folder_name),
)
artifacts[f"{stem}_contact_sheet"] = (
review_dir / f"{stem}_contact_sheet.html",
build_contact_sheet_html(folder_name),
)
artifacts["review_index_md"] = (review_dir / "index.md", build_review_index_markdown())
artifacts["review_index_html"] = (review_dir / "index.html", build_review_index_html())
written: dict[str, Path] = {}
for name, (path, payload) in artifacts.items():
path.parent.mkdir(parents=True, exist_ok=True)
if isinstance(payload, str):
path.write_text(payload, encoding="utf-8")
else:
path.write_text(json.dumps(payload, indent=indent) + "\n", encoding="utf-8")
written[name] = path
return written
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--limit", type=int, default=None, help="Limit selected pre-A/B variants.")
parser.add_argument("--indent", type=int, default=2, help="JSON indentation for written/printed artifacts.")
parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR, help="Artifact output directory.")
parser.add_argument(
"--artifact",
choices=(
"priority-plan",
"prompt-cue-batch",
"score-sheet",
"acceptance-gates",
"unused-pool-backlog",
"needs-samples-acquisition",
"needs-samples-acquisition-md",
"review-manifest",
"contact-sheet-html",
"review-index-md",
"review-index-html",
),
default="priority-plan",
help="Artifact to print when --write-artifacts is not set.",
)
parser.add_argument("--review-folder", default="reverse cowgirl", help="Folder to use for review-manifest/contact-sheet-html artifacts.")
parser.add_argument("--page-size", type=int, default=40, help="Review contact-sheet page size.")
parser.add_argument("--write-artifacts", action="store_true", help="Write all normal-camera prep artifacts.")
return parser
def main(argv: list[str] | None = None) -> int:
args = _parser().parse_args(argv)
if args.write_artifacts:
written = write_artifacts(args.output_dir, args.limit, args.indent)
for name, path in written.items():
print(f"{name}: {path}")
return 0
payloads: dict[str, Any] = {
"priority-plan": build_priority_plan(args.limit),
"prompt-cue-batch": build_prompt_cue_batch(args.limit),
"score-sheet": build_score_sheet(args.limit),
"acceptance-gates": build_acceptance_gates_markdown(),
"unused-pool-backlog": build_unused_pool_backlog(args.limit),
"needs-samples-acquisition": build_needs_samples_acquisition(args.limit),
"needs-samples-acquisition-md": build_needs_samples_acquisition_markdown(args.limit),
"review-manifest": build_review_manifest(str(args.review_folder), args.page_size),
"contact-sheet-html": build_contact_sheet_html(str(args.review_folder), args.page_size),
"review-index-md": build_review_index_markdown(),
"review-index-html": build_review_index_html(),
}
payload = payloads[args.artifact]
if isinstance(payload, str):
print(payload)
else:
print(json.dumps(payload, indent=args.indent))
return 0
if __name__ == "__main__":
raise SystemExit(main())