From 6ab082aa753063645367e1d27a27b71332bb75fd Mon Sep 17 00:00:00 2001
From: Tanner Prestegard <tanner.prestegard@ligo.org>
Date: Mon, 5 Nov 2018 15:28:00 -0600
Subject: [PATCH] Adding query by preferred event FAR for superevents

---
 gracedb/search/constants.py                   |  7 ++-
 gracedb/search/query/events.py                |  6 +-
 gracedb/search/query/superevents.py           | 55 +++++++++++++------
 gracedb/templates/search/query_help_frag.html |  6 ++
 4 files changed, 52 insertions(+), 22 deletions(-)

diff --git a/gracedb/search/constants.py b/gracedb/search/constants.py
index abc6adcc6..3df972b8b 100644
--- a/gracedb/search/constants.py
+++ b/gracedb/search/constants.py
@@ -1,13 +1,18 @@
+from pyparsing import Literal, Or
 
-# Translate comparison operators to Django queryset filter keywords
+# Create a parser for translating comparison operators to
+# Django queryset filter keywords
 EXPR_OPERATORS = {
     "<" :  "__lt",
     "<=":  "__lte",
     "=" :  "",
+    "==" :  "",
     ":" :  "",
     ">" :  "__gt",
     ">=":  "__gte",
 }
+ExpressionOperator = Or(map(Literal, EXPR_OPERATORS.keys()))
+ExpressionOperator.setParseAction(lambda toks: EXPR_OPERATORS[toks[0]])
 
 
 # Dict of LIGO run names (keys) and GPS time range tuples (values)
diff --git a/gracedb/search/query/events.py b/gracedb/search/query/events.py
index 8f614bfa4..0d0d51b57 100644
--- a/gracedb/search/query/events.py
+++ b/gracedb/search/query/events.py
@@ -25,7 +25,7 @@ nltime = nltime_.setParseAction(lambda toks: toks["calculatedTime"])
 from events.models import Group, Pipeline, Search, Label
 from .labels import getLabelQ
 from .superevents import parse_superevent_id, superevent_expr
-from ..constants import RUN_MAP, EXPR_OPERATORS
+from ..constants import RUN_MAP, ExpressionOperator
 from ..utils import maybeRange
 
 
@@ -178,10 +178,8 @@ lhs = delimitedList(Word(alphanums+'_'), '.')
 lhs.setParseAction(buildDjangoQueryField)
 rhs = afloat | QuotedString('"')
 
-op = Or(map(Literal, EXPR_OPERATORS.keys()))
-op.setParseAction(lambda toks: EXPR_OPERATORS[toks[0]])
 
-simpleTerm = lhs + op + rhs
+simpleTerm = lhs + ExpressionOperator + rhs
 simpleTerm.setParseAction(lambda toks: Q(**{toks[0]+toks[1]: toks[2]}))
 
 rangeTerm = lhs + Suppress('in') + rhs + Suppress(",") + rhs
diff --git a/gracedb/search/query/superevents.py b/gracedb/search/query/superevents.py
index af0c651ca..1d6b795d0 100644
--- a/gracedb/search/query/superevents.py
+++ b/gracedb/search/query/superevents.py
@@ -12,14 +12,14 @@ from events.models import Group, Pipeline, Search, Label
 from events.nltime import nlTimeExpression as nltime_
 from superevents.models import Superevent
 from .labels import getLabelQ
-from ..constants import RUN_MAP
+from ..constants import RUN_MAP, ExpressionOperator
 from ..utils import maybeRange
 
 from pyparsing import Word, nums, Literal, CaselessLiteral, delimitedList, \
     Suppress, QuotedString, Keyword, Combine, Or, Optional, OneOrMore, \
     ZeroOrMore, alphas, alphanums, Regex, opAssoc, operatorPrecedence, \
     oneOf, stringStart,  stringEnd, FollowedBy, ParseResults, ParseException, \
-    CaselessKeyword
+    CaselessKeyword, pyparsing_common
 
 # Set up logger
 logger = logging.getLogger(__name__)
@@ -171,6 +171,25 @@ parameter_dicts = {
             Q(category=[t[0] for t in Superevent.SUPEREVENT_CATEGORY_CHOICES
               if t[1].lower()==toks[0].lower()][0])),
     },
+    # far: 1e-5 SAME AS far == 1e-5. Other options like: far > 1e-5
+    'far': {
+        'doRange': False,
+        'value': Suppress(CaselessLiteral('far')) + ExpressionOperator + \
+            pyparsing_common.number,
+        'parseAction': lambda toks: ("far",
+            Q(**{'preferred_event__far' + toks[0]: toks[1]})),
+    },
+    # far in 1e-5, 1e-4 OR far: 1e-5 - 1e-4 OR far: 1e-5 .. 1e-4
+    'far_range': {
+        'doRange': False,
+        'value': Suppress(CaselessLiteral('far')) + \
+            Suppress(Or([CaselessLiteral(d) for d in ['in', ':']])) + \
+            pyparsing_common.number + \
+            Suppress(Or([CaselessLiteral(op) for op in [',', '..', '-']])) + \
+            pyparsing_common.number,
+        'parseAction': lambda toks: ("far_range",
+            Q(**{'preferred_event__far__range': toks.asList()})),
+    },
 }
 
 # Compile a list of expressions to try to match
@@ -180,33 +199,35 @@ for k,p in parameter_dicts.iteritems():
     # Define val and set name
     val = p['value']
     val.setName(k)
+    expr = val
 
     # Add keyword. Format is keyword: value
-    if isinstance(p['keyword'], list):
-        if p.has_key('keywordOptional') and p['keywordOptional']:
-            keyword_list = [Optional(Suppress(Keyword(k + ":"))) for k in
-                p['keyword']]
+    if p.has_key('keyword'):
+        if isinstance(p['keyword'], list):
+            if p.has_key('keywordOptional') and p['keywordOptional']:
+                keyword_list = [Optional(Suppress(Keyword(k + ":"))) for k in
+                    p['keyword']]
+            else:
+                keyword_list = [Suppress(Keyword(k + ":")) for k in p['keyword']]
+            keyword = reduce(lambda x,y: x^y, keyword_list)
         else:
-            keyword_list = [Suppress(Keyword(k + ":")) for k in p['keyword']]
-        keyword = reduce(lambda x,y: x^y, keyword_list)
-    else:
-        keyword = Suppress(Keyword(p['keyword'] + ":"))
-        if p.has_key('keywordOptional') and p['keywordOptional']:
-            keyword = Optional(keyword)
+            keyword = Suppress(Keyword(p['keyword'] + ":"))
+            if p.has_key('keywordOptional') and p['keywordOptional']:
+                keyword = Optional(keyword)
+
+        # Combine keyword and value into a single expression
+        expr = keyword + expr
 
     # Add range with format: parameter .. parameter
     if p.has_key('doRange') and p['doRange']:
         range_val = val + Suppress("..") + val
         val ^= range_val
 
-    # Combine keyword and value into a single expression
-    full_expr = keyword + val
-
     # Set parse action
-    full_expr = full_expr.setParseAction(p['parseAction'])
+    expr = expr.setParseAction(p['parseAction'])
 
     # Append to list of all expressions
-    expr_list.append(full_expr)
+    expr_list.append(expr)
 
 # Compile a combined expression by Or-ing all of the individual expressions
 combined_expr = Or(expr_list)
diff --git a/gracedb/templates/search/query_help_frag.html b/gracedb/templates/search/query_help_frag.html
index e9e135197..38d1c8a31 100644
--- a/gracedb/templates/search/query_help_frag.html
+++ b/gracedb/templates/search/query_help_frag.html
@@ -160,5 +160,11 @@
  </ul>
  Or, just add either "public" or "internal" to your query.
 
+ <h4>By preferred event FAR</h4>
+ <ul>
+   <li>far < 1e-5</li>
+   <li>FAR >= 0.01</li>
+   <li>far in 1e-7, 2.5e-6</li>
+ </ul>
 
 </div>
-- 
GitLab