@@ -373,6 +373,28 @@ export const loadProfiles = async (props: profileQueryType, db?: SupabaseDirectC
373373 )`
374374 }
375375
376+ // Decide whether unfilled (null) profiles should be surfaced by a numeric range
377+ // filter. Rare searches — a narrow band, or one sitting near an extreme edge of
378+ // the field's domain — exclude nulls, since people who left the field blank
379+ // almost certainly aren't the rare match being sought. Broad, central searches
380+ // keep nulls. `lo`/`hi` are the field's full domain (used when only one bound is
381+ // set); `isRare` receives the effective [m, M] range.
382+ const numericRangeClause = (
383+ field : string ,
384+ min : number | null | undefined ,
385+ max : number | null | undefined ,
386+ lo : number ,
387+ hi : number ,
388+ isRare : ( m : number , M : number ) => boolean ,
389+ ) => {
390+ if ( ! min && ! max ) return undefined
391+ const m = min ?? lo
392+ const M = max ?? hi
393+ const bounds = compact ( [ min && `${ field } >= $(min)` , max && `${ field } <= $(max)` ] ) . join ( ' AND ' )
394+ const clause = isRare ( m , M ) ? bounds : `(${ bounds } ) OR ${ field } IS NULL`
395+ return where ( clause , { min, max} )
396+ }
397+
376398 const filters = [
377399 where ( 'looking_for_matches = true' ) ,
378400 where ( `profiles.disabled != true` ) ,
@@ -415,63 +437,28 @@ export const loadProfiles = async (props: profileQueryType, db?: SupabaseDirectC
415437 pref_gender,
416438 } ) ,
417439
418- pref_age_min && where ( `age >= $(pref_age_min) or age is null` , { pref_age_min} ) ,
419-
420- pref_age_max && where ( `age <= $(pref_age_max) or age is null` , { pref_age_max} ) ,
421-
422- drinks_min &&
423- where ( `drinks_per_month >= $(drinks_min) or drinks_per_month is null` , { drinks_min} ) ,
424-
425- drinks_max &&
426- where ( `drinks_per_month <= $(drinks_max) or drinks_per_month is null` , { drinks_max} ) ,
427-
428- big5_openness_min &&
429- where ( `big5_openness >= $(big5_openness_min) or big5_openness is null` , { big5_openness_min} ) ,
430-
431- big5_openness_max &&
432- where ( `big5_openness <= $(big5_openness_max) or big5_openness is null` , { big5_openness_max} ) ,
433-
434- big5_conscientiousness_min &&
435- where (
436- `big5_conscientiousness >= $(big5_conscientiousness_min) or big5_conscientiousness is null` ,
437- { big5_conscientiousness_min} ,
438- ) ,
439-
440- big5_conscientiousness_max &&
441- where (
442- `big5_conscientiousness <= $(big5_conscientiousness_max) or big5_conscientiousness is null` ,
443- { big5_conscientiousness_max} ,
444- ) ,
445-
446- big5_extraversion_min &&
447- where ( `big5_extraversion >= $(big5_extraversion_min) or big5_extraversion is null` , {
448- big5_extraversion_min,
449- } ) ,
450-
451- big5_extraversion_max &&
452- where ( `big5_extraversion <= $(big5_extraversion_max) or big5_extraversion is null` , {
453- big5_extraversion_max,
454- } ) ,
455-
456- big5_agreeableness_min &&
457- where ( `big5_agreeableness >= $(big5_agreeableness_min) or big5_agreeableness is null` , {
458- big5_agreeableness_min,
459- } ) ,
460-
461- big5_agreeableness_max &&
462- where ( `big5_agreeableness <= $(big5_agreeableness_max) or big5_agreeableness is null` , {
463- big5_agreeableness_max,
464- } ) ,
465-
466- big5_neuroticism_min &&
467- where ( `big5_neuroticism >= $(big5_neuroticism_min) or big5_neuroticism is null` , {
468- big5_neuroticism_min,
469- } ) ,
470-
471- big5_neuroticism_max &&
472- where ( `big5_neuroticism <= $(big5_neuroticism_max) or big5_neuroticism is null` , {
473- big5_neuroticism_max,
474- } ) ,
440+ // age domain 18–100; young (<30), older (>50), or narrow (<15yr) searches are rare
441+ numericRangeClause ( 'age' , pref_age_min , pref_age_max , 18 , 100 , ( m , M ) => {
442+ return M < 30 || m > 50 || M - m < 15
443+ } ) ,
444+
445+ // drinks/month domain 0–20; narrow band, near-teetotal, or heavy-drinker searches are rare
446+ numericRangeClause ( 'drinks_per_month' , drinks_min , drinks_max , 0 , 20 , ( m , M ) => {
447+ return M - m < 5 || M < 5 || m > 15
448+ } ) ,
449+
450+ // big5 traits are 0–100 percentiles; narrow band or outer-quartile searches are rare
451+ ...(
452+ [
453+ [ 'big5_openness' , big5_openness_min , big5_openness_max ] ,
454+ [ 'big5_conscientiousness' , big5_conscientiousness_min , big5_conscientiousness_max ] ,
455+ [ 'big5_extraversion' , big5_extraversion_min , big5_extraversion_max ] ,
456+ [ 'big5_agreeableness' , big5_agreeableness_min , big5_agreeableness_max ] ,
457+ [ 'big5_neuroticism' , big5_neuroticism_min , big5_neuroticism_max ] ,
458+ ] as const
459+ ) . map ( ( [ field , min , max ] ) =>
460+ numericRangeClause ( field , min , max , 0 , 100 , ( m , M ) => M - m < 25 || M < 25 || m > 75 ) ,
461+ ) ,
475462
476463 pref_relation_styles ?. length &&
477464 where (
@@ -492,22 +479,24 @@ export const loadProfiles = async (props: profileQueryType, db?: SupabaseDirectC
492479 { diet} ,
493480 ) ,
494481
482+ // no dominant value, so unfilled profiles are never surfaced by a politics filter
495483 political_beliefs ?. length &&
496- where (
497- `political_beliefs IS NULL OR political_beliefs = '{}' OR political_beliefs && $(political_beliefs)` ,
498- { political_beliefs} ,
499- ) ,
484+ where ( `political_beliefs && $(political_beliefs)` , { political_beliefs} ) ,
500485
501486 relationship_status ?. length &&
502487 where (
503- `relationship_status IS NULL OR relationship_status = '{}' OR relationship_status && $(relationship_status)` ,
488+ // most are single, so we don't include the people who didn't answer unless looking for single
489+ ( ! relationship_status . includes ( 'single' )
490+ ? ''
491+ : "relationship_status IS NULL OR relationship_status = '{}' OR " ) +
492+ `relationship_status && $(relationship_status)` ,
504493 { relationship_status} ,
505494 ) ,
506495
507496 languages ?. length && where ( `languages && $(languages)` , { languages} ) ,
508497
509- religion ?. length &&
510- where ( `religion IS NULL OR religion = '{}' OR religion && $(religion)` , { religion} ) ,
498+ // no dominant value, so unfilled profiles are never surfaced by a religion filter
499+ religion ?. length && where ( `religion && $(religion)` , { religion} ) ,
511500
512501 orientation ?. length &&
513502 where (
0 commit comments