Skip to content

Commit

Permalink
enable similar taxon photos queries for subset of requests
Browse files Browse the repository at this point in the history
  • Loading branch information
pleary committed Jan 10, 2025
1 parent f98f596 commit 7d2ee89
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 7 deletions.
31 changes: 24 additions & 7 deletions lib/controllers/v1/computervision_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,17 @@ const ComputervisionController = class ComputervisionController {
formData.append( "lat", req.body.lat );
formData.append( "lng", req.body.lng );
}
if ( req.userSession?.isAdmin && (
req.body.include_representative_photos || req.query.include_representative_photos
) ) {

if ( config.imageProcesing.representativePhotoRolloutPercentage ) {
req.inat.isInRepresentativePhotosTestGroup = util.inRandomGrouping(
config.imageProcesing.representativePhotoRolloutPercentage
);
}
if ( (
req.userSession?.isAdmin && (
req.body.include_representative_photos || req.query.include_representative_photos
) ) || req.inat.isInRepresentativePhotosTestGroup
) {
formData.append( "return_embedding", "true" );
}

Expand Down Expand Up @@ -268,7 +276,7 @@ const ComputervisionController = class ComputervisionController {
return scores;
}

static async addRepresentativePhotos( results, embedding ) {
static async addRepresentativePhotos( req, results, embedding ) {
if ( _.isEmpty( embedding ) ) {
return;
}
Expand Down Expand Up @@ -322,6 +330,11 @@ const ComputervisionController = class ComputervisionController {
await pool.start( );

await ObservationPreload.assignObservationPhotoPhotos( representativeTaxonPhotos );
if ( req.inat.isInRepresentativePhotosTestGroup ) {
// if the request is in the test group, return after performing all queries,
// but before modifying the contents of the results
return;
}
_.each( representativeTaxonPhotos, taxonPhoto => {
if ( taxonPhoto?.photo?.url ) {
taxonPhoto.taxon.representative_photo = taxonPhoto.photo;
Expand Down Expand Up @@ -387,10 +400,14 @@ const ComputervisionController = class ComputervisionController {
} );
}

if ( req.userSession?.isAdmin && (
req.body.include_representative_photos || req.query.include_representative_photos
) ) {
if ( (
req.userSession?.isAdmin && (
req.body.include_representative_photos || req.query.include_representative_photos
) ) || req.inat.isInRepresentativePhotosTestGroup
) {
req.inat.requestContext = "representativePhotos";
await ComputervisionController.addRepresentativePhotos(
req,
withTaxa,
visionApiResponse.embedding
);
Expand Down
2 changes: 2 additions & 0 deletions lib/logstasher.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ const Logstasher = class Logstasher {
payload.subtype = "ClientMessage";
payload.error_message = req.body.message;
}
} else if ( req.inat.requestContext ) {
payload.context = req.inat.requestContext;
}
return payload;
}
Expand Down
15 changes: 15 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,21 @@ const util = class util {
}
return null;
}

// accepts a `percentage` parameter which is a number from 0 to 100. A `percentage`
// of 0 will always return false. A `percentage` of 100 will always return true. All
// other values will return true `percentage`% of the time
static inRandomGrouping( percentage = 0 ) {
if ( !_.isNumber( percentage ) || percentage <= 0 ) {
return false;
}
if ( percentage >= 100 ) {
return true;
}
// return true if the requested value is less than or equal to a random
// number from 1 to 100 inclusive
return percentage >= ( _.random( 99 ) + 1 );
}
};

util.iucnValues = {
Expand Down
18 changes: 18 additions & 0 deletions test/logstasher.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,22 @@ describe( "Logstasher", ( ) => {
{ headers: { "x-real-ip": "127.0.0.1, 192.168.1.1" } }
) ).to.eq( "192.168.1.1" );
} );

describe( "afterRequestPayload", ( ) => {
it( "includes request context", ( ) => {
const req = { inat: { requestContext: "inatContext" } };
expect( Logstasher.afterRequestPayload( req ).context ).to.eq( "inatContext" );
} );

it( "prioritizes context from log response bodies", ( ) => {
const req = {
_logClientError: true,
inat: { requestContext: "inatContext" },
body: {
context: "logContext"
}
};
expect( Logstasher.afterRequestPayload( req ).context ).to.eq( "logContext" );
} );
} );
} );
28 changes: 28 additions & 0 deletions test/util.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const _ = require( "lodash" );
const { expect } = require( "chai" );
const crypto = require( "crypto" );
const util = require( "../lib/util" );
Expand Down Expand Up @@ -306,4 +307,31 @@ describe( "util", ( ) => {
} ) ).to.eq( `ObservationsController.search-fields-${fieldsHash}` );
} );
} );

describe( "inRandomGrouping", ( ) => {
it( "returns true when percentage is at or above 100", ( ) => {
let samples = _.times( 10000, util.inRandomGrouping( 100 ) );
expect( _.every( samples, sample => sample === true ) );
samples = _.times( 10000, util.inRandomGrouping( 110 ) );
expect( _.every( samples, sample => sample === true ) );
} );

it( "returns false when percentage is at or below 0", ( ) => {
let samples = _.times( 10000, util.inRandomGrouping( 0 ) );
expect( _.every( samples, sample => sample === false ) );
samples = _.times( 10000, util.inRandomGrouping( -1 ) );
expect( _.every( samples, sample => sample === false ) );
} );

it( "returns true roughly `percentage` percent of the time", ( ) => {
const testPercentages = [1, 5, 10, 20, 30, 40, 50, 60, 70, 80, 99];
_.each( testPercentages, testPercentage => {
const samples = _.times( 100000, ( ) => util.inRandomGrouping( testPercentage ) );
const countTrue = _.filter( samples, sample => sample === true ).length;
const truePercentage = ( countTrue / samples.length ) * 100;
expect( truePercentage ).to.be.below( testPercentage + 2 );
expect( truePercentage ).to.be.above( testPercentage - 2 );
} );
} );
} );
} );

0 comments on commit 7d2ee89

Please sign in to comment.