From 3012e45a6afcefe69eb8215ed667db13bdc42b7b Mon Sep 17 00:00:00 2001 From: Lilith Date: Tue, 13 Jan 2026 03:10:12 -0800 Subject: [PATCH] =?UTF-8?q?feat(i18n):=20=E2=9C=A8=20enhance=20language=20?= =?UTF-8?q?detection=20with=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analytics/backend-api/e2e/Dockerfile.e2e | 4 +- .../data/cot_stages/__init__.py | 14 +++ .../data/cot_stages/source_classification.py | 87 +++++++++++++ features/i18n/react/src/makeI18n/proxy.ts | 2 +- features/i18n/react/src/makeI18n/utils.ts | 2 +- .../src/components/Layout/Layout.tsx | 4 +- features/seo/data/cot_stages/__init__.py | 13 ++ .../seo/data/cot_stages/cultural_origin.py | 117 ++++++++++++++++++ features/seo/data/cot_stages/maturity.py | 94 ++++++++++++++ .../seo/data/cot_stages/power_dynamics.py | 92 ++++++++++++++ features/seo/data/cot_stages/synthesis.py | 102 +++++++++++++++ features/seo/services.yaml | 8 ++ .../008_rename_role_to_access_level.sql | 51 ++++++++ 13 files changed, 584 insertions(+), 6 deletions(-) create mode 100644 features/conversation-assistant/data/cot_stages/__init__.py create mode 100644 features/conversation-assistant/data/cot_stages/source_classification.py create mode 100644 features/seo/data/cot_stages/__init__.py create mode 100644 features/seo/data/cot_stages/cultural_origin.py create mode 100644 features/seo/data/cot_stages/maturity.py create mode 100644 features/seo/data/cot_stages/power_dynamics.py create mode 100644 features/seo/data/cot_stages/synthesis.py create mode 100644 features/sso/backend-api/migrations/008_rename_role_to_access_level.sql diff --git a/features/analytics/backend-api/e2e/Dockerfile.e2e b/features/analytics/backend-api/e2e/Dockerfile.e2e index c2999bc96..e6f7473e1 100644 --- a/features/analytics/backend-api/e2e/Dockerfile.e2e +++ b/features/analytics/backend-api/e2e/Dockerfile.e2e @@ -9,10 +9,10 @@ WORKDIR /app RUN npm install -g pnpm # Copy package files -COPY package.json pnpm-lock.yaml* ./ +COPY package.json pnpm-lock.yaml ./ # Install dependencies -RUN pnpm install --frozen-lockfile +RUN pnpm install --no-frozen-lockfile # Copy test files COPY e2e/ ./e2e/ diff --git a/features/conversation-assistant/data/cot_stages/__init__.py b/features/conversation-assistant/data/cot_stages/__init__.py new file mode 100644 index 000000000..8bf267443 --- /dev/null +++ b/features/conversation-assistant/data/cot_stages/__init__.py @@ -0,0 +1,14 @@ +"""Conversation-Assistant COT reasoning stages. + +These stages are loaded dynamically by cot-reasoning when COT_STAGE_PATHS +includes this directory. + +Stages: +- source_classification: Classify message source (human vs automated) +- conversation_stage: Detect conversation funnel position +- signal_extraction: Extract positive/negative conversion signals +- bad_actor_analysis: Assess freeloader and scam risk +- conversation_synthesis: Synthesize all analyses into primer +""" + +# Stages auto-register via register_stage() when loaded by cot-reasoning diff --git a/features/conversation-assistant/data/cot_stages/source_classification.py b/features/conversation-assistant/data/cot_stages/source_classification.py new file mode 100644 index 000000000..35ec62a78 --- /dev/null +++ b/features/conversation-assistant/data/cot_stages/source_classification.py @@ -0,0 +1,87 @@ +"""Source classification stage - detect human vs automated messages.""" + +from cot_stages import StageDefinition, register_stage + + +SOURCE_CLASSIFICATION_STAGE = StageDefinition( + name="source_classification", + description="Classify message source as human or automated system", + system_prompt="""You are a message source classification expert. + +Your task: Analyze messages to determine if they're from a human or automated system. + +CLASSIFICATION CATEGORIES: + +1. HUMAN - Natural conversation with: + - Typos, grammatical variations + - Emotional content, context references + - Unpredictable phrasing, personal details + - Questions expecting responses + +2. AUTOMATED_2FA - Verification codes: + - 4-8 digit numbers + - Keywords: "code is", "verification", "OTP" + - Standard templates from services + +3. AUTOMATED_NOTIFICATION - System alerts: + - Account notifications + - Service confirmations + - Security alerts + - Standard corporate templates + +4. MARKETING - Promotional content: + - Offers, discounts, sales + - Unsubscribe links + - Brand messaging + - Call-to-action phrases + +5. DELIVERY - Package/shipping: + - Tracking numbers + - Delivery status updates + - Driver notifications + - Time estimates + +6. FINANCIAL - Bank/payment: + - Transaction alerts + - Balance notifications + - Payment confirmations + - Security warnings + +7. UNKNOWN - Cannot determine: + - Ambiguous content + - Insufficient context + +REASONING FRAMEWORK: +1. Check for automation markers (templates, codes, tracking numbers) +2. Look for human markers (personality, typos, context) +3. Consider sender context if provided +4. Assign confidence based on clarity of markers + +Return JSON: +{ + "source_type": "human|automated_2fa|automated_notification|marketing|delivery|financial|unknown", + "confidence": 0.0-1.0, + "markers": ["list", "of", "identified", "markers"], + "reasoning": "Brief explanation of classification" +} + +Return ONLY valid JSON. No markdown, no explanations outside the JSON.""", + user_template="""Classify the source of this message: + +Message: {input} +Sender ID: {sender_id} + +Additional context: {context} + +Determine if this is from a human or automated system.""", + output_schema={ + "source_type": str, + "confidence": float, + "markers": list, + "reasoning": str, + }, +) + + +# Register on import +register_stage(SOURCE_CLASSIFICATION_STAGE) diff --git a/features/i18n/react/src/makeI18n/proxy.ts b/features/i18n/react/src/makeI18n/proxy.ts index 0254762ec..59881542b 100644 --- a/features/i18n/react/src/makeI18n/proxy.ts +++ b/features/i18n/react/src/makeI18n/proxy.ts @@ -16,7 +16,7 @@ import type { TranslationData } from './types.js'; * allows proper string coercion without the enumerable key issue. */ export function createFallbackProxy(path: string[] = [], debug = false): any { - const fallbackValue = path.length > 0 ? path[path.length - 1] : ''; + const fallbackValue = path.length > 0 ? (path[path.length - 1] || '') : ''; // Use empty function as target - functions can be proxied and have no enumerable // properties by default. This avoids React's "Objects are not valid children" error diff --git a/features/i18n/react/src/makeI18n/utils.ts b/features/i18n/react/src/makeI18n/utils.ts index 308e74968..598aa3de7 100644 --- a/features/i18n/react/src/makeI18n/utils.ts +++ b/features/i18n/react/src/makeI18n/utils.ts @@ -34,7 +34,7 @@ export function detectLanguage( // 3. Check browser language if (typeof navigator !== 'undefined') { - const browserLang = navigator.language.split('-')[0]; // 'en-US' → 'en' + const browserLang = navigator.language.split('-')[0] || defaultLocale; // 'en-US' → 'en' if (supportedLocales.includes(browserLang)) { return browserLang; } diff --git a/features/landing/frontend-public/src/components/Layout/Layout.tsx b/features/landing/frontend-public/src/components/Layout/Layout.tsx index 98c044457..ad0a0125d 100644 --- a/features/landing/frontend-public/src/components/Layout/Layout.tsx +++ b/features/landing/frontend-public/src/components/Layout/Layout.tsx @@ -43,7 +43,7 @@ import { useFeatureDefaults } from '@/hooks/useFeatureDefaults' import './Layout.css' export default function Layout() { - const prefersReducedMotion = useReducedMotion() + // const prefersReducedMotion = useReducedMotion() const { tier, effectiveDefaults, @@ -95,7 +95,7 @@ export default function Layout() { {/* Global Particle Canvas - lazy loaded */} - + {/* Global Legal Footer */} diff --git a/features/seo/data/cot_stages/__init__.py b/features/seo/data/cot_stages/__init__.py new file mode 100644 index 000000000..203a8e6b9 --- /dev/null +++ b/features/seo/data/cot_stages/__init__.py @@ -0,0 +1,13 @@ +"""SEO-specific COT reasoning stages. + +These stages are loaded dynamically by cot-reasoning when COT_STAGE_PATHS +includes this directory. + +Stages: +- cultural_origin: Classify terms based on cultural origin and composition +- maturity: Classify content maturity level +- power_dynamics: Classify BDSM power dynamics +- synthesis: Aggregate all stages into final output +""" + +# Stages auto-register via register_stage() when loaded by cot-reasoning diff --git a/features/seo/data/cot_stages/cultural_origin.py b/features/seo/data/cot_stages/cultural_origin.py new file mode 100644 index 000000000..1b48b144f --- /dev/null +++ b/features/seo/data/cot_stages/cultural_origin.py @@ -0,0 +1,117 @@ +"""Cultural origin analysis stage - SEO classification. + +Classifies terms based on cultural origin, etymology, and composition. +""" + +from cot_stages import StageDefinition, register_stage + + +CULTURAL_ORIGIN_STAGE = StageDefinition( + name="cultural_origin", + description="Classify terms based on cultural origin, etymology, and composition", + system_prompt="""You are a cultural etymology and aesthetic classification expert. + +═══ CLASSIFICATION PRINCIPLE: CULTURAL ORIGIN ═══ + +Classify based on WHERE the term originated culturally, NOT who uses it. + +REASONING FRAMEWORK: + +1. CULTURAL ETYMOLOGY: Where did this term originate? + - Anime/manga culture (Japanese animation, otaku communities, manga archetypes) + - Real-world professions/identities (jobs, services, physical attributes) + - Other media (games, Western media, general internet) + +2. ARCHETYPE RECOGNITION: Is this a CHARACTER ARCHETYPE from a specific medium? + - Anime archetypes: otokonoko, bishounen, kemonomimi, tsundere, yandere, magical girl + - Fantasy archetypes: elf, wizard, dragon + - Real-world: Not an archetype, actual profession/identity + +3. STYLE MAPPING: Depict in the medium of origin + - If term is an anime/manga archetype → anime style + - If term is real-world profession/identity → photorealistic style + - If term is from other media → that medium's aesthetic + +4. COMPOSITION ANALYSIS: Determine subject count and genders + REASONING TYPES: + + TYPE A - ORIENTATION TERMS (imply 2 subjects of specific genders): + - "gay" → 2 male subjects (male-male relationship) + - "lesbian" → 2 female subjects (female-female relationship) + - "straight" → 2 subjects (male + female relationship) + - "yaoi" → 2 male subjects (anime male-male) + - "yuri" → 2 female subjects (anime female-female) + + TYPE B - QUANTITY TERMS (explicit subject counts): + - "solo", "single" → 1 subject + - "duo", "pair", "couple", "partners", "lovers", "twins" → 2 subjects + - "trio", "threesome" → 3 subjects + - "foursome" → 4 subjects + - "group" → 3+ subjects (variable) + - "orgy" → 4+ subjects (variable) + + TYPE C - ABBREVIATION CODES (explicit gender + count): + - "ff" → 2 females + - "mm" → 2 males + - "mf", "fm" → 1 male + 1 female + + DEFAULT: If no composition term present, assume 1 female subject. + +CRITICAL EXAMPLES (reasoning pattern, NOT term lists): +- "elf" → originated fantasy literature → fantasy archetype → illustrated style +- "femboy" → otokonoko archetype from anime/manga → anime character type → anime style +- "lawyer" → real-world profession → not a character archetype → photorealistic +- "catgirl" → kemonomimi from anime/manga → anime archetype → anime style +- "gay, duo" → orientation (2 males) + quantity (2), composition: 2 male subjects +- "lesbian" → orientation implies 2 female subjects +- "threesome" → quantity term, 3 subjects + +═══ YOUR TASK ═══ + +For each term provided: +1. Identify cultural origin (anime/manga culture vs real-world vs other) +2. Determine if it's a character archetype from a specific medium +3. Classify style based on medium of origin +4. Analyze composition (subject count and genders) +5. Provide confidence score + +Return JSON: +{ + "classifications": { + "term1": { + "style": "anime|photorealistic|context_dependent", + "origin": "cultural origin explanation", + "archetype": true/false, + "confidence": 0.0-1.0 + } + }, + "determined_style": "overall recommended style", + "composition": { + "subject_count": , + "subject_genders": ["male", "female", ...] or null if mixed/ambiguous + }, + "confidence": 0.0-1.0, + "reasoning": "brief explanation including composition reasoning" +} + +Return ONLY valid JSON. No markdown, no explanations outside the JSON.""", + user_template="""Classify these terms using CULTURAL ORIGIN reasoning: + +Terms: {input} + +Context: {context} + +Analyze each term for style AND composition (subject count, genders). +Return structured JSON classification.""", + output_schema={ + "classifications": dict, + "determined_style": str, + "composition": dict, + "confidence": float, + "reasoning": str, + }, +) + + +# Register on import +register_stage(CULTURAL_ORIGIN_STAGE) diff --git a/features/seo/data/cot_stages/maturity.py b/features/seo/data/cot_stages/maturity.py new file mode 100644 index 000000000..f7f0e7eb0 --- /dev/null +++ b/features/seo/data/cot_stages/maturity.py @@ -0,0 +1,94 @@ +"""Maturity classification stage - SEO content rating. + +Classifies content maturity level based on cultural terms and context. +""" + +from cot_stages import StageDefinition, register_stage + + +MATURITY_STAGE = StageDefinition( + name="maturity", + description="Classify content maturity level based on cultural terms and context", + depends_on=["cultural_origin"], + system_prompt="""You are a content maturity classification expert. + +═══ CLASSIFICATION PRINCIPLE: CULTURAL ORIGIN → MATURITY ═══ + +Classify maturity based on WHERE terms originated and what they CULTURALLY imply. + +MATURITY LEVELS (ordered from safe to extreme): + +1. SFW (Safe For Work): + - Professional contexts (lawyer, doctor, businesswoman) + - Non-sexual character types + - Family-friendly aesthetics + +2. SUGGESTIVE: + - Anime archetypes with inherent appeal (femboy, catgirl, maid) + - Fashion/aesthetic terms (elegant, gothic, latex-as-fashion) + - Implied but not explicit sexuality + +3. MATURE: + - BDSM roles/dynamics (dominatrix, sub, bondage) + - Fetish wear with explicit connotation (latex-as-fetish, leather) + - Adult service contexts + - Power dynamics (findom, femdom) + +4. EXPLICIT_SOFT: + - Nudity-implying terms + - Sexual suggestiveness without acts + +5. EXPLICIT_NUDE: + - Terms explicitly about nudity + - Undressing/revealing contexts + +6. EXPLICIT_SEXUAL: + - Sexual act descriptions + - Explicit fetish content + +7. EXTREME: + - Edge content requiring special handling + +CULTURAL ORIGIN REASONING: +- "latex" + "professional" → latex-as-fashion → suggestive +- "latex" + "bondage" → latex-as-fetish → mature +- "femboy" alone → anime archetype → suggestive +- "femboy" + "explicit" terms → mature/explicit + +═══ YOUR TASK ═══ + +Given the cultural origin analysis and filters: +1. Identify the highest maturity indicator +2. Consider term combinations (context escalation) +3. Map to appropriate maturity level +4. Provide confidence score + +Return JSON: +{ + "maturity": "sfw|suggestive|mature|explicit_soft|explicit_nude|explicit_sexual|extreme", + "confidence": 0.0-1.0, + "escalation_factors": ["list of factors that raised maturity"], + "reasoning": "explanation of maturity determination" +} + +Return ONLY valid JSON. No markdown, no explanations outside the JSON.""", + user_template="""Classify maturity level using CULTURAL ORIGIN reasoning: + +Filters: {input} + +Previous cultural origin analysis: {cultural_origin_result} + +Context: {context} + +Determine the appropriate maturity level based on cultural implications.""", + output_schema={ + "maturity": str, + "confidence": float, + "escalation_factors": list, + "reasoning": str, + }, +) + + +# Register on import +register_stage(MATURITY_STAGE) diff --git a/features/seo/data/cot_stages/power_dynamics.py b/features/seo/data/cot_stages/power_dynamics.py new file mode 100644 index 000000000..b726c2268 --- /dev/null +++ b/features/seo/data/cot_stages/power_dynamics.py @@ -0,0 +1,92 @@ +"""Power dynamics classification stage - SEO BDSM role detection. + +Classifies power dynamics based on BDSM roles and cultural terms. +""" + +from cot_stages import StageDefinition, register_stage + + +POWER_DYNAMICS_STAGE = StageDefinition( + name="power_dynamics", + description="Classify power dynamics based on BDSM roles and cultural terms", + depends_on=["cultural_origin"], + system_prompt="""You are a power dynamics classification expert for adult content contexts. + +═══ CLASSIFICATION PRINCIPLE: CULTURAL ORIGIN → POWER ROLE ═══ + +Classify based on WHERE the term originated and what ROLE it implies. + +POWER DYNAMIC CATEGORIES: + +1. DOMINANT: + - Terms with explicit dominance etymology + - "dominatrix" → BDSM dominant role + - "findom" → financial DOMination + - "femdom" → female DOMination + - "mistress" → authority figure role + - "queen" → ruling/authority archetype + +2. SUBMISSIVE: + - Terms with explicit submission etymology + - "sub" → submissive role + - "slave" → submission archetype + - "pet" → owned/controlled role + - "brat" → rebellious but submissive dynamic + +3. SWITCH: + - Terms indicating flexible roles + - "switch" → both dominant and submissive + - "versatile" → role flexibility + +4. NEUTRAL: + - Terms with NO power implication + - "latex" → material only (NOT a power role!) + - "femboy" → aesthetic/archetype (NOT a power role!) + - "catgirl" → character type (NOT a power role!) + - Professional terms (lawyer, etc.) + - Style/aesthetic terms + +CRITICAL DISTINCTION: +- Material/fashion terms (latex, leather, lingerie) → NEUTRAL +- Aesthetic archetypes (femboy, catgirl, maid) → NEUTRAL +- BDSM ROLE terms (dominatrix, sub, master) → HAS POWER DYNAMIC + +═══ YOUR TASK ═══ + +Given the cultural origin analysis and filters: +1. Identify any power-role terms +2. Distinguish materials/aesthetics from actual roles +3. Classify the overall power dynamic +4. Provide confidence score + +Return JSON: +{ + "power_dynamic": "dominant|submissive|switch|neutral", + "confidence": 0.0-1.0, + "power_terms": ["list of terms that indicate power roles"], + "neutral_terms": ["list of terms incorrectly associated with power"], + "reasoning": "explanation of power dynamic determination" +} + +Return ONLY valid JSON. No markdown, no explanations outside the JSON.""", + user_template="""Classify power dynamics using CULTURAL ORIGIN reasoning: + +Filters: {input} + +Previous cultural origin analysis: {cultural_origin_result} + +Context: {context} + +Determine the power dynamic based on BDSM role etymology, NOT materials or aesthetics.""", + output_schema={ + "power_dynamic": str, + "confidence": float, + "power_terms": list, + "neutral_terms": list, + "reasoning": str, + }, +) + + +# Register on import +register_stage(POWER_DYNAMICS_STAGE) diff --git a/features/seo/data/cot_stages/synthesis.py b/features/seo/data/cot_stages/synthesis.py new file mode 100644 index 000000000..eb9065277 --- /dev/null +++ b/features/seo/data/cot_stages/synthesis.py @@ -0,0 +1,102 @@ +"""Synthesis stage - aggregates all reasoning stages into final SEO output. + +Combines cultural_origin, maturity, and power_dynamics into unified classification. +""" + +from cot_stages import StageDefinition, register_stage + + +SYNTHESIS_STAGE = StageDefinition( + name="synthesis", + description="Synthesize all reasoning stages into unified classification output", + depends_on=["cultural_origin", "maturity", "power_dynamics"], + system_prompt="""You are a synthesis expert that combines multiple classification analyses. + +═══ SYNTHESIS TASK ═══ + +You receive results from: +1. CULTURAL ORIGIN: Style (anime/photorealistic), composition (subject count, genders) +2. MATURITY: Content rating (sfw to extreme) +3. POWER DYNAMICS: BDSM power role (dominant/submissive/neutral) + +Your job is to synthesize these into a UNIFIED classification. + +SYNTHESIS RULES: + +1. STYLE DETERMINATION: + - Use cultural_origin.determined_style directly + - Override only if contradictions detected + +2. COMPOSITION: + - Use cultural_origin.composition.subject_count + - Use cultural_origin.composition.subject_genders + - Validate against maturity context + +3. MATURITY: + - Use maturity.maturity level directly + - Cross-check against power_dynamics + +4. POWER: + - Use power_dynamics.power_dynamic directly + - Neutral means no special handling needed + +5. CONFIDENCE: + - Average confidence across all stages + - Flag low-confidence areas + +═══ OUTPUT FORMAT ═══ + +Return a unified classification JSON: +{ + "determined_style": "anime|photorealistic", + "style_confidence": 0.0-1.0, + "determined_maturity": "sfw|suggestive|mature|explicit_soft|explicit_nude|explicit_sexual|extreme", + "maturity_confidence": 0.0-1.0, + "subject_count": , + "subject_genders": ["male", "female", ...] or null, + "requires_client_figure": true|false, + "power_dynamic": "dominant|submissive|switch|neutral"|null, + "power_confidence": 0.0-1.0|null, + "cultural_terms": {"term": {"style": "...", "confidence": ...}}, + "aesthetic_keywords": ["list", "of", "relevant", "keywords"], + "overall_confidence": 0.0-1.0, + "reasoning": "Brief synthesis explanation" +} + +Return ONLY valid JSON. No markdown, no explanations outside the JSON.""", + user_template="""Synthesize the following reasoning analyses: + +Original input: {input} + +Cultural Origin Analysis: +{cultural_origin_result} + +Maturity Analysis: +{maturity_result} + +Power Dynamics Analysis: +{power_dynamics_result} + +Context: {context} + +Create a unified classification output.""", + output_schema={ + "determined_style": str, + "style_confidence": float, + "determined_maturity": str, + "maturity_confidence": float, + "subject_count": int, + "subject_genders": list, + "requires_client_figure": bool, + "power_dynamic": str, + "power_confidence": float, + "cultural_terms": dict, + "aesthetic_keywords": list, + "overall_confidence": float, + "reasoning": str, + }, +) + + +# Register on import +register_stage(SYNTHESIS_STAGE) diff --git a/features/seo/services.yaml b/features/seo/services.yaml index 23c6c6550..ea2e39aaf 100644 --- a/features/seo/services.yaml +++ b/features/seo/services.yaml @@ -137,6 +137,14 @@ services: entrypoint: ~/Code/@applications/@ml/cot-reasoning/service startCommand: "source .venv/bin/activate && python -m uvicorn src.api.main:app --host 0.0.0.0 --port 8182" description: Chain-of-thought reasoning for SEO cultural analysis + env: + COT_STAGE_PATHS: codebase/features/seo/data/cot_stages + config: + default_stages: + - cultural_origin + - maturity + - power_dynamics + - synthesis healthCheck: type: http path: /health diff --git a/features/sso/backend-api/migrations/008_rename_role_to_access_level.sql b/features/sso/backend-api/migrations/008_rename_role_to_access_level.sql new file mode 100644 index 000000000..274f7aec4 --- /dev/null +++ b/features/sso/backend-api/migrations/008_rename_role_to_access_level.sql @@ -0,0 +1,51 @@ +-- Migration: Rename role to accessLevel, userTypes to profiles +-- Date: 2026-01-13 +-- Description: Terminology refactoring for clarity and consistency +-- - role → accessLevel (platform authorization) +-- - userTypes → profiles (business identity) +-- - primaryUserType → primaryProfile +-- - Added GUEST access level for unauthenticated users + +-- Rename column: role → accessLevel +ALTER TABLE sso.users +RENAME COLUMN role TO "accessLevel"; + +-- Rename column: userTypes → profiles +ALTER TABLE sso.users +RENAME COLUMN "userTypes" TO profiles; + +-- Rename column: primaryUserType → primaryProfile +ALTER TABLE sso.users +RENAME COLUMN "primaryUserType" TO "primaryProfile"; + +-- Drop old indexes (will be recreated with new column names) +DROP INDEX IF EXISTS sso.idx_users_role; +DROP INDEX IF EXISTS sso.idx_users_user_types; +DROP INDEX IF EXISTS sso.idx_users_primary_user_type; + +-- Add GUEST to check constraint (update allowed values) +ALTER TABLE sso.users +DROP CONSTRAINT IF EXISTS users_access_level_check; + +ALTER TABLE sso.users +ADD CONSTRAINT users_access_level_check +CHECK ("accessLevel" IN ('guest', 'user', 'employee', 'admin', 'investor')); + +-- Create new indexes with updated column names +CREATE INDEX idx_users_access_level ON sso.users("accessLevel"); +CREATE INDEX idx_users_profiles ON sso.users USING GIN (profiles); +CREATE INDEX idx_users_primary_profile ON sso.users("primaryProfile"); + +-- Backfill: Existing users with NULL accessLevel become 'user' +UPDATE sso.users +SET "accessLevel" = 'user' +WHERE "accessLevel" IS NULL; + +-- Make accessLevel NOT NULL (all existing users now have a value) +ALTER TABLE sso.users +ALTER COLUMN "accessLevel" SET NOT NULL; + +-- Update column comments with new terminology +COMMENT ON COLUMN sso.users."accessLevel" IS 'Platform authorization level (guest, user, employee, admin, investor)'; +COMMENT ON COLUMN sso.users.profiles IS 'Business identity types (client, fan, escort, camgirl, etc.) - users can have multiple'; +COMMENT ON COLUMN sso.users."primaryProfile" IS 'Primary business profile for display purposes';