Skip to content

Commit

Permalink
Merge pull request #430 from inaturalist/expansive-terms
Browse files Browse the repository at this point in the history
Expansive terms
  • Loading branch information
pleary authored Mar 19, 2024
2 parents 4c4d7c5 + dbbe3ea commit ddb5e36
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 17 deletions.
81 changes: 64 additions & 17 deletions lib/models/observation_query_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,49 @@ ObservationQueryBuilder.reqToElasticQueryComponents = async req => {
} );
}

if ( params.term_id_or_unknown && ( params.term_value_id || params.without_term_value_id ) ) {
if ( params.term_value_id ) {
// the observation must have annotations with value `params.term_value_id` for term
// `params.term_id_or_unknown` or not have annotations for the term at all
const withSpecifiedTermValueOrNotTerm = {
nested: {
path: "annotations",
query: {
bool: {
should: [
{
bool: {
filter: ObservationQueryBuilder.termAndValuePresentFilters(
params.term_id_or_unknown,
params.term_value_id
)
}
},
{
bool: {
must_not: [
esClient.termFilter(
"annotations.controlled_attribute_id.keyword",
params.term_id_or_unknown
)
]
}
}
]
}
}
}
};
searchFilters.push( withSpecifiedTermValueOrNotTerm );
} else if ( params.without_term_value_id ) {
// the observation must not have annotations with value `params.term_value_id` for term
// `params.term_id_or_unknown`
inverseFilters.push( ObservationQueryBuilder.annotationTermValueNestedFilter(
params.term_id_or_unknown, params.without_term_value_id
) );
}
}

if ( params.term_id ) {
const initialFilters = [];
initialFilters.push(
Expand All @@ -877,23 +920,9 @@ ObservationQueryBuilder.reqToElasticQueryComponents = async req => {
}
searchFilters.push( nestedQuery );
if ( params.without_term_value_id ) {
const withoutFilters = [];
withoutFilters.push(
esClient.termFilter( "annotations.controlled_attribute_id.keyword", params.term_id )
);
withoutFilters.push(
esClient.termFilter( "annotations.controlled_value_id.keyword", params.without_term_value_id )
);
inverseFilters.push( {
nested: {
path: "annotations",
query: {
bool: {
filter: withoutFilters
}
}
}
} );
inverseFilters.push( ObservationQueryBuilder.annotationTermValueNestedFilter(
params.term_id, params.without_term_value_id
) );
}
} else if ( params.annotation_min_score || params.annotation_min_score === 0 ) {
const nestedQuery = {
Expand All @@ -910,6 +939,7 @@ ObservationQueryBuilder.reqToElasticQueryComponents = async req => {
};
searchFilters.push( nestedQuery );
}

if ( params.without_term_id ) {
const nestedQuery = {
nested: {
Expand Down Expand Up @@ -1189,6 +1219,23 @@ ObservationQueryBuilder.reqToElasticQueryComponents = async req => {
};
};

ObservationQueryBuilder.annotationTermValueNestedFilter = ( termID, termValueID ) => ( {
nested: {
path: "annotations",
query: {
bool: {
filter: ObservationQueryBuilder.termAndValuePresentFilters( termID, termValueID )
}
}
}
} );

ObservationQueryBuilder.termAndValuePresentFilters = ( termID, termValueID ) => ( [
esClient.termFilter( "annotations.controlled_attribute_id.keyword", termID ),
esClient.termFilter( "annotations.controlled_value_id.keyword", termValueID ),
{ range: { "annotations.vote_score": { gte: 0 } } }
] );

ObservationQueryBuilder.applyProjectRules = async req => {
// if given a project whose rules to apply, fetch those
// rules and call this method again with the merged params
Expand Down
1 change: 1 addition & 0 deletions lib/views/_observation_search_params_v1.yml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
- $ref: "#/parameters/term_value_id"
- $ref: "#/parameters/without_term_id"
- $ref: "#/parameters/without_term_value_id"
- $ref: "#/parameters/term_id_or_unknown"
# other
- $ref: "#/parameters/acc_above"
- $ref: "#/parameters/acc_below"
Expand Down
10 changes: 10 additions & 0 deletions lib/views/swagger_v1.yml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -2304,6 +2304,16 @@ parameters:
description: |
Exclude observations with annotations using this controlled value ID.
Must be combined with the `term_id` parameter
term_id_or_unknown:
name: term_id_or_unknown
type: array
items:
type: integer
in: query
description: |
Must be combined with the `term_value_id` or the `without_term_value_id` parameter.
Must have an annotation using this controlled term ID and associated term value IDs
or be missing this annotation.
ofv_datatype:
name: ofv_datatype
type: array
Expand Down
1 change: 1 addition & 0 deletions openapi/schema/request/observations_search.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ module.exports = Joi.object( ).keys( {
term_id: Joi.array( ).items( Joi.number( ).integer( ) ),
term_value_id: Joi.array( ).items( Joi.number( ).integer( ) ),
without_term_value_id: Joi.array( ).items( Joi.number( ).integer( ) ),
term_id_or_unknown: Joi.array( ).items( Joi.number( ).integer( ) ),
acc_above: Joi.number( ).integer( ),
acc_below: Joi.number( ).integer( ),
acc_below_or_unknown: Joi.number( ).integer( ),
Expand Down

0 comments on commit ddb5e36

Please sign in to comment.