mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 10:19:43 +00:00
Add inverse_of tracking and admin question management
- Add inverse_of column support for question pair linking - Enhance admin questions page with bulk operations - Add DB migrations for inverse_of and FK indexes - Update pack generator docs for inverse linking - Improve Docker configs for admin app
This commit is contained in:
1 parent
efe93a3db4
commit
df955812fe
22 files changed
+9951
-121
No files matched your search
@@ -21,7 +21,9 @@ You are a content generation specialist for Sauci, a couples intimacy app. You c
|
||||
|
||||
3. **No time words**: Never tonight/now/today/right now
|
||||
|
||||
4. **Inverse requirement**: Every asymmetric question MUST have its inverse created
|
||||
4. **Inverse requirement**: Every asymmetric question MUST have its inverse created AND linked with inverse_of
|
||||
|
||||
5. **Database linking**: Use the `inverse_of` column to link inverse pairs (inverse points to primary's UUID)
|
||||
|
||||
### Intensity Scale
|
||||
|
||||
@@ -39,20 +41,32 @@ You are a content generation specialist for Sauci, a couples intimacy app. You c
|
||||
```
|
||||
text: "Cook dinner together"
|
||||
partner_text: NULL
|
||||
inverse_of: NULL
|
||||
```
|
||||
|
||||
**Use partner_text when roles differ:**
|
||||
```
|
||||
-- PRIMARY question (inverse_of = NULL)
|
||||
id: uuid-1
|
||||
text: "Give your partner oral"
|
||||
partner_text: "Receive oral from your partner"
|
||||
inverse_of: NULL
|
||||
```
|
||||
|
||||
**CRITICAL - Always create the inverse:**
|
||||
**CRITICAL - Always create the inverse AND link it:**
|
||||
```
|
||||
-- INVERSE question (inverse_of = primary's UUID)
|
||||
id: uuid-2
|
||||
text: "Receive oral from your partner"
|
||||
partner_text: "Give your partner oral"
|
||||
inverse_of: uuid-1
|
||||
```
|
||||
|
||||
The `inverse_of` column ensures we can:
|
||||
- Track which questions are pairs
|
||||
- Calculate unique question count (not inflated by inverses)
|
||||
- Display accurate pack sizes to users
|
||||
|
||||
### Avoid
|
||||
|
||||
- Cheesy language ("make love", "souls connecting")
|
||||
@@ -70,9 +84,9 @@ partner_text: "Give your partner oral"
|
||||
1. **Understand the brief**: Category, intensity range, pack position in progression
|
||||
2. **Research existing content**: Check what packs exist, avoid overlap
|
||||
3. **Brainstorm themes**: What specific activities fit this pack?
|
||||
4. **Generate questions**: Mix symmetric and asymmetric, create all inverses
|
||||
5. **Review**: Check all rules, remove duplicates, verify intensity
|
||||
6. **Format**: Output as SQL or table format
|
||||
4. **Generate questions**: Mix symmetric and asymmetric, create all inverses with inverse_of links
|
||||
5. **Review**: Check all rules, remove duplicates, verify intensity, confirm inverse pairs are linked
|
||||
6. **Format**: Output as SQL with inverse_of column properly set
|
||||
|
||||
## Output Format
|
||||
|
||||
|
||||
@@ -10,12 +10,13 @@ Fix questions in category: $ARGUMENTS
|
||||
|
||||
## Task Overview
|
||||
|
||||
Analyze questions across all packs in a category and apply fixes for issues across 5 dimensions:
|
||||
Analyze questions across all packs in a category and apply fixes for issues across 6 dimensions:
|
||||
1. **Text Phrasing** - Improve wording, remove wishy-washy language
|
||||
2. **Audience Targeting** - Fix couple/initiator gender targeting
|
||||
3. **Intensity Levels** - Correct misassigned intensity ratings
|
||||
4. **Required Props** - Identify missing physical props/accessories
|
||||
5. **Deletions** - Remove true duplicates, unsafe, or broken questions
|
||||
5. **Inverse Linking** - Detect and link inverse question pairs using inverse_of column
|
||||
6. **Deletions** - Remove true duplicates, unsafe, or broken questions
|
||||
|
||||
## Step 1: Parse Arguments
|
||||
|
||||
@@ -47,8 +48,8 @@ ORDER BY p.name;
|
||||
```
|
||||
|
||||
```sql
|
||||
-- Get questions for all packs in the category
|
||||
SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props, p.name as pack_name
|
||||
-- Get questions for all packs in the category (include inverse_of for pair detection)
|
||||
SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props, q.inverse_of, p.name as pack_name
|
||||
FROM questions q
|
||||
JOIN question_packs p ON q.pack_id = p.id
|
||||
WHERE p.category_id = '<category_id>' AND q.deleted_at IS NULL
|
||||
@@ -138,10 +139,36 @@ Check if current intensity matches the activity:
|
||||
**Rules:**
|
||||
- Props are physical items/accessories, not body parts or acts
|
||||
- Use lowercase, short, singular names (e.g., "blindfold", "remote vibrator")
|
||||
- Try to avoid generics such as "toy" (unless the activity can be done with any toy)
|
||||
- Reuse existing prop names from the catalog when possible
|
||||
- If no props are required, set to null
|
||||
|
||||
### 3.5 Deletion Analysis
|
||||
### 3.5 Inverse Linking Analysis
|
||||
|
||||
**Purpose:** Detect inverse question pairs and link them using the `inverse_of` database column.
|
||||
|
||||
**Detection Algorithm:**
|
||||
1. For each asymmetric question (has partner_text), look for its inverse:
|
||||
- Inverse exists if another question has: `text` = this question's `partner_text` AND `partner_text` = this question's `text`
|
||||
2. Check if the `inverse_of` column is correctly set:
|
||||
- One question in the pair should be the "primary" (inverse_of = NULL)
|
||||
- The other should be the "inverse" (inverse_of = primary's UUID)
|
||||
|
||||
**Fixing Missing Links:**
|
||||
- If inverse pair exists but neither has `inverse_of` set:
|
||||
- Set the first one (by created_at) as primary (inverse_of = NULL)
|
||||
- Set the second one's inverse_of = primary's UUID
|
||||
- If inverse pair exists but both have `inverse_of = NULL`:
|
||||
- Choose one as primary, link the other
|
||||
- If a question has `inverse_of` pointing to a non-existent question:
|
||||
- Find the actual inverse and fix the link, OR set to NULL if no inverse exists
|
||||
|
||||
**Important:**
|
||||
- Only asymmetric questions (with partner_text) should have inverse pairs
|
||||
- Symmetric questions (partner_text = NULL) should have inverse_of = NULL
|
||||
- This fixes the database structure, NOT the question content
|
||||
|
||||
### 3.6 Deletion Analysis
|
||||
|
||||
**Deletion Categories:**
|
||||
- `duplicate`: Same `text` AND same initiator as another question
|
||||
@@ -188,6 +215,7 @@ Present findings grouped by fix type:
|
||||
| Intensity fixes | X | X |
|
||||
| Targeting fixes | X | X |
|
||||
| Props fixes | X | X |
|
||||
| Inverse linking fixes | X | X |
|
||||
| Deletions | X | X |
|
||||
| No changes needed | X | - |
|
||||
|
||||
@@ -198,11 +226,13 @@ For each question needing changes, show:
|
||||
```
|
||||
**Question ID:** <id>
|
||||
**Current:** <text> / <partner_text or null>
|
||||
**Current inverse_of:** <uuid or null>
|
||||
**Issues:**
|
||||
- Text: <issue description>
|
||||
- Intensity: <current> -> <suggested> (<reason>)
|
||||
- Targeting: <issue description>
|
||||
- Props: <current> -> <suggested>
|
||||
- Inverse Link: <missing/broken> - <inverse question id if found>
|
||||
- DELETE: <category> - <reason>
|
||||
|
||||
**Fix:**
|
||||
@@ -212,6 +242,7 @@ For each question needing changes, show:
|
||||
- Couple Targets: <new allowed_couple_genders or null>
|
||||
- Initiator Targets: <new target_user_genders or null>
|
||||
- Required Props: <new required_props or null>
|
||||
- inverse_of: <uuid or null>
|
||||
```
|
||||
|
||||
## Step 5: Apply Fixes (if --apply flag)
|
||||
@@ -237,6 +268,12 @@ UPDATE questions SET
|
||||
required_props = ARRAY['...'] or NULL
|
||||
WHERE id = '<question_id>';
|
||||
|
||||
-- Inverse linking fixes
|
||||
-- Link inverse question to its primary
|
||||
UPDATE questions SET inverse_of = '<primary_question_id>' WHERE id = '<inverse_question_id>';
|
||||
-- Or clear a broken inverse_of link
|
||||
UPDATE questions SET inverse_of = NULL WHERE id = '<question_id>';
|
||||
|
||||
-- Deletions (soft delete)
|
||||
UPDATE questions SET deleted_at = NOW() WHERE id IN ('<id1>', '<id2>', ...);
|
||||
```
|
||||
@@ -265,7 +302,9 @@ If --apply is NOT set, output the SQL statements for manual review.
|
||||
- DO prioritize safety issues (delete unsafe content first)
|
||||
- DO preserve the core intent of questions when suggesting text fixes
|
||||
- DO verify inverse pairs exist before flagging duplicates
|
||||
- DO detect and link inverse pairs using the inverse_of column
|
||||
- DON'T change questions that are already well-formed
|
||||
- DON'T suggest overly restrictive targeting (prefer inclusive by default)
|
||||
- DON'T create new prop names when existing ones match
|
||||
- DON'T flag inverse pairs as duplicates (one's text = other's partner_text)
|
||||
- DON'T forget to set inverse_of for detected inverse pairs - this ensures accurate unique question counts
|
||||
@@ -91,21 +91,33 @@ Follow these rules strictly:
|
||||
- BAD: "Receive oral from your partner" (clinical)
|
||||
- GOOD: "Let your partner pleasure you with their mouth" (enticing)
|
||||
|
||||
#### 3.4 Critical: Inverse Questions
|
||||
#### 3.4 Critical: Inverse Questions and Database Linking
|
||||
|
||||
For EVERY asymmetric question, create its inverse:
|
||||
For EVERY asymmetric question, create its inverse AND link them using the `inverse_of` column:
|
||||
|
||||
```
|
||||
Original:
|
||||
Original (PRIMARY - inverse_of = NULL):
|
||||
id: <generated_uuid_1>
|
||||
text: "Spank your partner"
|
||||
partner_text: "Be spanked by your partner"
|
||||
inverse_of: NULL
|
||||
|
||||
Inverse (MUST CREATE):
|
||||
Inverse (SECONDARY - points to primary):
|
||||
id: <generated_uuid_2>
|
||||
text: "Be spanked by your partner"
|
||||
partner_text: "Spank your partner"
|
||||
inverse_of: <generated_uuid_1>
|
||||
```
|
||||
|
||||
This ensures both partners are asked about both roles.
|
||||
This ensures:
|
||||
1. Both partners are asked about both roles
|
||||
2. The database tracks which questions are inverses of each other
|
||||
3. Accurate unique question counts (200 questions = 100 unique concepts with inverses)
|
||||
|
||||
**Database Schema:**
|
||||
- `inverse_of` column: UUID referencing the primary question's ID
|
||||
- Primary questions have `inverse_of = NULL`
|
||||
- Inverse questions have `inverse_of = <primary_question_id>`
|
||||
|
||||
#### 3.5 Intensity Guidelines
|
||||
|
||||
@@ -195,15 +207,38 @@ VALUES (
|
||||
)
|
||||
RETURNING id;
|
||||
|
||||
-- Then insert questions (use returned pack_id)
|
||||
INSERT INTO questions (pack_id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props) VALUES
|
||||
('[pack_id]', 'Question text', 'Partner text or NULL', intensity, NULL, NULL, NULL),
|
||||
('[pack_id]', 'Blowjob question', 'Partner text', 3, ARRAY['male+male', 'female+male'], ARRAY['female'], NULL),
|
||||
('[pack_id]', 'Toy question', 'Partner text', 4, NULL, NULL, ARRAY['remote vibrator']),
|
||||
-- ... all questions
|
||||
-- Then insert questions with inverse_of linking
|
||||
-- For inverse pairs: insert primary first (inverse_of = NULL), then inverse pointing to primary
|
||||
|
||||
-- Generate UUIDs for inverse pairs upfront
|
||||
-- Pair 1: Primary and its inverse
|
||||
WITH pair1_primary AS (
|
||||
INSERT INTO questions (pack_id, text, partner_text, intensity, inverse_of)
|
||||
VALUES ('[pack_id]', 'Spank your partner', 'Be spanked by your partner', 3, NULL)
|
||||
RETURNING id
|
||||
)
|
||||
INSERT INTO questions (pack_id, text, partner_text, intensity, inverse_of)
|
||||
SELECT '[pack_id]', 'Be spanked by your partner', 'Spank your partner', 3, id
|
||||
FROM pair1_primary;
|
||||
|
||||
-- Symmetric questions (no inverse_of needed)
|
||||
INSERT INTO questions (pack_id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props, inverse_of) VALUES
|
||||
('[pack_id]', 'Symmetric question text', NULL, 2, NULL, NULL, NULL, NULL);
|
||||
|
||||
-- Or bulk insert with explicit UUIDs for pairs:
|
||||
INSERT INTO questions (id, pack_id, text, partner_text, intensity, inverse_of) VALUES
|
||||
-- Pair 1
|
||||
('uuid-primary-1', '[pack_id]', 'Give your partner a massage', 'Receive a massage from your partner', 2, NULL),
|
||||
('uuid-inverse-1', '[pack_id]', 'Receive a massage from your partner', 'Give your partner a massage', 2, 'uuid-primary-1'),
|
||||
-- Pair 2
|
||||
('uuid-primary-2', '[pack_id]', 'Blindfold your partner', 'Be blindfolded by your partner', 3, NULL),
|
||||
('uuid-inverse-2', '[pack_id]', 'Be blindfolded by your partner', 'Blindfold your partner', 3, 'uuid-primary-2'),
|
||||
-- Symmetric (no pair)
|
||||
(gen_random_uuid(), '[pack_id]', 'Cook dinner together', NULL, 1, NULL);
|
||||
```
|
||||
|
||||
**Field notes:**
|
||||
- `inverse_of`: NULL for primary/symmetric questions, UUID of primary question for inverses
|
||||
- `allowed_couple_genders`: NULL for all couples, or array like `ARRAY['male+male', 'female+male']`
|
||||
- `target_user_genders`: NULL for any initiator, or array like `ARRAY['female']`
|
||||
- `required_props`: NULL if no props needed, or array like `ARRAY['blindfold', 'ice cubes']`
|
||||
@@ -213,13 +248,14 @@ Also provide a summary table:
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Total questions | X |
|
||||
| Unique question concepts | X (total - inverse count) |
|
||||
| Intensity 1 | X |
|
||||
| Intensity 2 | X |
|
||||
| Intensity 3 | X |
|
||||
| Intensity 4 | X |
|
||||
| Intensity 5 | X |
|
||||
| Symmetric (null partner_text) | X |
|
||||
| Asymmetric (with inverses) | X pairs |
|
||||
| Asymmetric pairs (with inverse_of links) | X pairs (X questions) |
|
||||
| With couple targeting | X |
|
||||
| With initiator targeting | X |
|
||||
| With required props | X |
|
||||
@@ -236,10 +272,12 @@ Also provide a summary table:
|
||||
|
||||
- DO ask clarifying questions if the pack concept is unclear
|
||||
- DO check existing packs in the category first
|
||||
- DO create inverse questions for ALL asymmetric content
|
||||
- DO create inverse questions for ALL asymmetric content AND link them with inverse_of
|
||||
- DO vary sentence structure and openers
|
||||
- DO use the inverse_of column to link inverse pairs (inverse points to primary's UUID)
|
||||
- DON'T use cheesy or cliche language
|
||||
- DON'T assume heterosexual couples
|
||||
- DON'T skip the inverse question requirement
|
||||
- DON'T forget to set inverse_of - this is how we track unique question concepts
|
||||
- DON'T use interview-style questions ("Would you like to...")
|
||||
- DON'T treat inverse pairs as duplicates - they are REQUIRED (one's `text` = other's `partner_text`)
|
||||
@@ -10,12 +10,13 @@ Review and validate questions: $ARGUMENTS
|
||||
|
||||
## Task Overview
|
||||
|
||||
Analyze questions for issues and suggest fixes across 5 dimensions:
|
||||
Analyze questions for issues and suggest fixes across 6 dimensions:
|
||||
1. **Text Phrasing** - Improve wording, remove wishy-washy language
|
||||
2. **Audience Targeting** - Fix couple/initiator gender targeting
|
||||
3. **Intensity Levels** - Correct misassigned intensity ratings
|
||||
4. **Required Props** - Identify missing physical props/accessories
|
||||
5. **Deletions** - Flag duplicates, redundant, or broken questions
|
||||
5. **Inverse Linking** - Detect and link inverse question pairs using inverse_of column
|
||||
6. **Deletions** - Flag duplicates, redundant, or broken questions
|
||||
|
||||
## Step 1: Parse Arguments
|
||||
|
||||
@@ -29,18 +30,18 @@ Extract from $ARGUMENTS:
|
||||
Query the database:
|
||||
|
||||
```sql
|
||||
-- If pack name provided:
|
||||
SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props
|
||||
-- If pack name provided (include inverse_of for pair detection):
|
||||
SELECT q.id, q.text, q.partner_text, q.intensity, q.allowed_couple_genders, q.target_user_genders, q.required_props, q.inverse_of
|
||||
FROM questions q
|
||||
JOIN question_packs p ON q.pack_id = p.id
|
||||
WHERE p.name ILIKE '%<pack_name>%'
|
||||
WHERE p.name ILIKE '%<pack_name>%' AND q.deleted_at IS NULL
|
||||
ORDER BY q.created_at
|
||||
LIMIT <limit>;
|
||||
|
||||
-- If pack ID provided:
|
||||
SELECT id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props
|
||||
SELECT id, text, partner_text, intensity, allowed_couple_genders, target_user_genders, required_props, inverse_of
|
||||
FROM questions
|
||||
WHERE pack_id = '<pack_id>'
|
||||
WHERE pack_id = '<pack_id>' AND deleted_at IS NULL
|
||||
ORDER BY created_at
|
||||
LIMIT <limit>;
|
||||
```
|
||||
@@ -138,7 +139,20 @@ Check if current intensity matches the activity:
|
||||
- Reuse existing prop names from the catalog when possible
|
||||
- If no props are required, set to null
|
||||
|
||||
### 4.5 Deletion Analysis
|
||||
### 4.5 Inverse Linking Analysis
|
||||
|
||||
**Purpose:** Detect inverse question pairs and link them using the `inverse_of` database column.
|
||||
|
||||
**Detection:**
|
||||
1. For each asymmetric question (has partner_text), look for its inverse:
|
||||
- Inverse exists if another question has: `text` = this question's `partner_text` AND `partner_text` = this question's `text`
|
||||
2. Check if the `inverse_of` column is correctly set
|
||||
|
||||
**Fixes:**
|
||||
- If pair exists but not linked: set inverse_of on the second question to point to the first
|
||||
- Symmetric questions should have inverse_of = NULL
|
||||
|
||||
### 4.6 Deletion Analysis
|
||||
|
||||
**Deletion Categories:**
|
||||
- `duplicate`: Same core action as another question
|
||||
@@ -184,6 +198,7 @@ Present findings in a structured format:
|
||||
| Intensity fixes | X |
|
||||
| Targeting fixes | X |
|
||||
| Props fixes | X |
|
||||
| Inverse linking fixes | X |
|
||||
| Deletions suggested | X |
|
||||
| No changes needed | X |
|
||||
|
||||
@@ -196,12 +211,14 @@ For each question needing changes:
|
||||
**Current Text:** <text>
|
||||
**Current Partner Text:** <partner_text or null>
|
||||
**Current Intensity:** <intensity>
|
||||
**Current inverse_of:** <uuid or null>
|
||||
|
||||
Issues Found:
|
||||
- [ ] Text: <description of issue>
|
||||
- [ ] Intensity: <current> -> <suggested> (<reason>)
|
||||
- [ ] Targeting: <description>
|
||||
- [ ] Props: <current> -> <suggested>
|
||||
- [ ] Inverse Link: <missing/broken> - <inverse question id if found>
|
||||
- [ ] DELETE: <category> - <reason>
|
||||
|
||||
Suggested Fix:
|
||||
@@ -211,6 +228,7 @@ Suggested Fix:
|
||||
- Couple Targets: <new allowed_couple_genders or null>
|
||||
- Initiator Targets: <new target_user_genders or null>
|
||||
- Required Props: <new required_props or null>
|
||||
- inverse_of: <uuid or null>
|
||||
```
|
||||
|
||||
### SQL to Apply Fixes
|
||||
@@ -236,8 +254,11 @@ UPDATE questions SET
|
||||
required_props = ARRAY[<new_props>]
|
||||
WHERE id = '<question_id>';
|
||||
|
||||
-- Deletions
|
||||
DELETE FROM questions WHERE id IN ('<id1>', '<id2>', ...);
|
||||
-- Inverse linking fixes
|
||||
UPDATE questions SET inverse_of = '<primary_question_id>' WHERE id = '<inverse_question_id>';
|
||||
|
||||
-- Deletions (soft delete)
|
||||
UPDATE questions SET deleted_at = NOW() WHERE id IN ('<id1>', '<id2>', ...);
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
@@ -254,6 +275,9 @@ DELETE FROM questions WHERE id IN ('<id1>', '<id2>', ...);
|
||||
- DO group similar issues together for batch fixes
|
||||
- DO prioritize safety issues (delete unsafe content first)
|
||||
- DO preserve the core intent of questions when suggesting text fixes
|
||||
- DO detect and link inverse pairs using the inverse_of column
|
||||
- DON'T change questions that are already well-formed
|
||||
- DON'T suggest overly restrictive targeting (prefer inclusive by default)
|
||||
- DON'T create new prop names when existing ones match
|
||||
- DON'T flag inverse pairs as duplicates (one's text = other's partner_text)
|
||||
- DON'T forget to set inverse_of for detected inverse pairs
|
||||
Reference in new issue
Block a user