diff --git a/android/app/build.gradle b/android/app/build.gradle index 494efd8a..7fc2779e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -11,7 +11,7 @@ android { applicationId "com.compassconnections.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 87 + versionCode 88 versionName "1.19.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" aaptOptions { diff --git a/backend/api/package.json b/backend/api/package.json index 343bb0c9..127b6f5e 100644 --- a/backend/api/package.json +++ b/backend/api/package.json @@ -1,6 +1,6 @@ { "name": "@compass/api", - "version": "1.34.0", + "version": "1.34.1", "private": true, "description": "Backend API endpoints", "main": "src/serve.ts", diff --git a/backend/api/src/llm-extract-profile.ts b/backend/api/src/llm-extract-profile.ts index cbc9a613..2661bec3 100644 --- a/backend/api/src/llm-extract-profile.ts +++ b/backend/api/src/llm-extract-profile.ts @@ -57,7 +57,9 @@ async function validateProfileFields( llmProfile: Partial, validChoices: Record, ): Promise> { - const result: Partial> = {...llmProfile} + const result: Partial> = { + ...removeNullOrUndefinedProps(llmProfile), + } const toArray: (keyof ProfileWithoutUser)[] = [ 'diet', @@ -188,6 +190,35 @@ async function validateProfileFields( ) } + // Validate age preferences + if (result.pref_age_min !== undefined) { + if ( + !Number.isFinite(result.pref_age_min) || + result.pref_age_min < 18 || + result.pref_age_min > 100 + ) { + result.pref_age_min = undefined + } + } + + if (result.pref_age_max !== undefined) { + if ( + !Number.isFinite(result.pref_age_max) || + result.pref_age_max < 18 || + result.pref_age_max > 100 + ) { + result.pref_age_max = undefined + } + } + + // Ensure pref_age_max > pref_age_min when both are defined + if (result.pref_age_min !== undefined && result.pref_age_max !== undefined) { + if (result.pref_age_max <= result.pref_age_min) { + result.pref_age_max = undefined + result.pref_age_min = undefined + } + } + return result } @@ -215,7 +246,7 @@ async function setCachedResult(cacheKey: string, result: any): Promise { await fs.mkdir(CACHE_DIR, {recursive: true}) const cacheFile = join(CACHE_DIR, `${cacheKey}.json`) await fs.writeFile(cacheFile, JSON.stringify(result), 'utf-8') - debug('Cached LLM result', {cacheKey: cacheKey.substring(0, 8)}) + debug('Cached LLM result', {cacheKey: cacheKey.substring(0, 8), result}) } catch (error) { log('Failed to write cache', {cacheKey, error}) // Don't throw - caching failure shouldn't break the main flow @@ -307,6 +338,7 @@ async function callGemini(text: string) { ] for (const model of models) { + debug(`Calling Gemini ${model}...`) const response = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`, { @@ -423,7 +455,7 @@ export async function callLLM( const PROFILE_FIELDS: Partial> = { // Basic info - age: 'Number. Age in years.', + age: 'Number. Age in years (between 18 and 100).', gender: `String. One of: ${validChoices.pref_gender?.join(', ')}. If multiple mentioned, use the most likely one. Infer if you have enough evidence`, height_in_inches: 'Number. Height converted to inches.', city: 'String. Current city of residence (English spelling).', @@ -475,8 +507,10 @@ export async function callLLM( 'String. Free-form elaboration on political views, only if explicitly stated.', // Preferences - pref_age_min: 'Number. Minimum preferred age of match.', - pref_age_max: 'Number. Maximum preferred age of match.', + pref_age_min: + 'Number. Minimum preferred age of match (higher than 18, only if mentioned, do NOT infer).', + pref_age_max: + 'Number. Maximum preferred age of match (lower than 100, only if mentioned, do NOT infer).', pref_gender: `Array. Any of: ${validChoices.pref_gender?.join(', ')}`, pref_relation_styles: `Array. Any of: ${validChoices.pref_relation_styles?.join(', ')}`, pref_romantic_styles: `Array. Any of: ${validChoices.pref_romantic_styles?.join(', ')}`, @@ -520,6 +554,7 @@ TEXT TO ANALYZE: debug({text}) const outputText = await callGemini(text) + // const outputText = {pref_age_min: 0, pref_age_max: 120} if (!outputText) { throw APIErrors.internalServerError('Failed to parse LLM response') diff --git a/backend/shared/src/parse.ts b/backend/shared/src/parse.ts index 8fee225c..6d77a744 100644 --- a/backend/shared/src/parse.ts +++ b/backend/shared/src/parse.ts @@ -523,9 +523,9 @@ function cleanHref(href: string): string { export function extractGoogleDocId(url: string) { const patterns = [ - /\/document\/d\/([a-zA-Z0-9-_]+)/, // standard /d/{id}/ format - /id=([a-zA-Z0-9-_]+)/, // ?id= query param format - /^([a-zA-Z0-9-_]+)$/, // raw ID passed directly + /\/document\/d\/([a-zA-Z0-9-_]+)\/edit/, // standard /d/{id}/edit format + // /id=([a-zA-Z0-9-_]+)/, // ?id= query param format (catches false negatives) + // /^([a-zA-Z0-9-_]+)$/, // raw ID passed directly (catches false negatives) ] for (const pattern of patterns) {