-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.AR.Rules
  ( rules
  ) where

import Data.HashMap.Strict (HashMap)
import Data.String
import Data.Text (Text)
import Prelude
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text

import Duckling.Dimensions.Types
import Duckling.Numeral.Helpers
import Duckling.Quantity.Helpers
import Duckling.Regex.Types
import Duckling.Types
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

quantities :: [(Text, String, TQuantity.Unit)]
quantities =
  [ ("<quantity> cups", "(ÙƒÙˆØ¨(Ø§Ù†|ÙŠÙ†)?|[Ø£Ø§]ÙƒÙˆØ§Ø¨)", TQuantity.Cup)
  , ("<quantity> grams", "(((ÙƒÙŠÙ„Ùˆ|Ù…ÙŠ?Ù„ÙŠ?) ?)?((Øº|Ø¬)Ø±Ø§Ù…(Ø§Øª|ÙŠÙ†|Ø§Ù†)?)|Ùƒ(Øº|Ø¬)Ù…?|Ù…Ù„(Øº|Ø¬)|(Øº|Ø¬)Ù…)", TQuantity.Gram)
  , ("<quantity> lb", "(Ø¨Ø§ÙˆÙ†Ø¯(Ø§Ù†|ÙŠÙ†)?)", TQuantity.Pound)
  , ("<quantity> oz", "([Ø£Ø§]ÙˆÙ†Øµ([Ø©Ù‡]|ØªØ§Ù†|ØªÙŠÙ†|Ø§Øª))", TQuantity.Ounce)
  ]

opsMap :: HashMap Text (Double -> Double)
opsMap = HashMap.fromList
  [ ( "ØºØ±Ø§Ù…Ø§Ù†", (* 2))
  , ( "ØºØ±Ø§Ù…ÙŠÙ†", (* 2))
  , ( "ÙƒÙˆØ¨Ø§Ù†", (* 2))
  , ( "ÙƒÙˆØ¨ÙŠÙ†", (* 2))
  , ( "Ø¨Ø§ÙˆÙ†Ø¯Ø§Ù†", (* 2))
  , ( "Ø¨Ø§ÙˆÙ†Ø¯ÙŠÙ†", (* 2))
  , ( "Ø§ÙˆÙ†ØµØªØ§Ù†", (* 2))
  , ( "Ø§ÙˆÙ†ØµØªÙŠÙ†", (* 2))
  , ( "Ø£ÙˆÙ†ØµØªØ§Ù†", (* 2))
  , ( "Ø£ÙˆÙ†ØµØªÙŠÙ†", (* 2))
  , ( "Ø¬Ø±Ø§Ù…Ø§Ù†", (* 2))
  , ( "Ø¬Ø±Ø§Ù…ÙŠÙ†", (* 2))
  , ( "Ù…ÙŠÙ„ÙŠ ØºØ±Ø§Ù…Ø§Ù†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠØºØ±Ø§Ù…Ø§Ù†", (/ 500))
  , ( "Ù…ÙŠÙ„ØºØ±Ø§Ù…Ø§Ù†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠ ØºØ±Ø§Ù…ÙŠÙ†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠØºØ±Ø§Ù…ÙŠÙ†", (/ 500))
  , ( "Ù…ÙŠÙ„ØºØ±Ø§Ù…ÙŠÙ†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠ Ø¬Ø±Ø§Ù…Ø§Ù†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠØ¬Ø±Ø§Ù…Ø§Ù†", (/ 500))
  , ( "Ù…ÙŠÙ„Ø¬Ø±Ø§Ù…Ø§Ù†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠ Ø¬Ø±Ø§Ù…ÙŠÙ†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠØ¬Ø±Ø§Ù…ÙŠÙ†", (/ 500))
  , ( "Ù…ÙŠÙ„Ø¬Ø±Ø§Ù…ÙŠÙ†", (/ 500))
  , ( "Ù…ÙŠÙ„ÙŠ ØºØ±Ø§Ù…", (/ 1000))
  , ( "Ù…ÙŠÙ„ÙŠØºØ±Ø§Ù…", (/ 1000))
  , ( "Ù…ÙŠÙ„ØºØ±Ø§Ù…", (/ 1000))
  , ( "ÙƒÙŠÙ„ÙˆØºØ±Ø§Ù…", (* 1000))
  , ( "ÙƒÙŠÙ„Ùˆ ØºØ±Ø§Ù…", (* 1000))
  , ( "Ù…ÙŠÙ„ÙŠ ØºØ±Ø§Ù…Ø§Øª", (/ 1000))
  , ( "Ù…ÙŠÙ„ÙŠØºØ±Ø§Ù…Ø§Øª", (/ 1000))
  , ( "Ù…ÙŠÙ„ØºØ±Ø§Ù…Ø§Øª", (/ 1000))
  , ( "Ù…Ù„Øº", (/ 1000))
  , ( "ÙƒØº", (* 1000))
  , ( "ÙƒØºÙ…", (* 1000))
  , ( "Ù…ÙŠÙ„ÙŠ Ø¬Ø±Ø§Ù…", (/ 1000))
  , ( "Ù…ÙŠÙ„ÙŠØ¬Ø±Ø§Ù…", (/ 1000))
  , ( "Ù…ÙŠÙ„Ø¬Ø±Ø§Ù…", (/ 1000))
  , ( "Ù…ÙŠÙ„ÙŠ Ø¬Ø±Ø§Ù…Ø§Øª", (/ 1000))
  , ( "Ù…ÙŠÙ„ÙŠØ¬Ø±Ø§Ù…Ø§Øª", (/ 1000))
  , ( "Ù…ÙŠÙ„Ø¬Ø±Ø§Ù…Ø§Øª", (/ 1000))
  , ( "ÙƒÙŠÙ„ÙˆØºØ±Ø§Ù…Ø§Øª", (* 1000))
  , ( "ÙƒÙŠÙ„Ùˆ ØºØ±Ø§Ù…Ø§Øª", (* 1000))
  , ( "Ù…Ù„Ø¬", (/ 1000))
  , ( "ÙƒÙŠÙ„ÙˆØ¬Ø±Ø§Ù…", (* 1000))
  , ( "ÙƒÙŠÙ„Ùˆ Ø¬Ø±Ø§Ù…", (* 1000))
  , ( "ÙƒÙŠÙ„ÙˆØ¬Ø±Ø§Ù…Ø§Øª", (* 1000))
  , ( "ÙƒÙŠÙ„Ùˆ Ø¬Ø±Ø§Ù…Ø§Øª", (* 1000))
  , ( "ÙƒØ¬", (* 1000))
  , ( "ÙƒØ¬Ù…", (* 1000))
  , ( "ÙƒÙŠÙ„ÙˆØºØ±Ø§Ù…Ø§Ù†", (* 2000))
  , ( "ÙƒÙŠÙ„ÙˆØºØ±Ø§Ù…ÙŠÙ†", (* 2000))
  , ( "ÙƒÙŠÙ„Ùˆ ØºØ±Ø§Ù…Ø§Ù†", (* 2000))
  , ( "ÙƒÙŠÙ„Ùˆ ØºØ±Ø§Ù…ÙŠÙ†", (* 2000))
  , ( "ÙƒÙŠÙ„ÙˆØ¬Ø±Ø§Ù…Ø§Ù†", (* 2000))
  , ( "ÙƒÙŠÙ„ÙˆØ¬Ø±Ø§Ù…ÙŠÙ†", (* 2000))
  , ( "ÙƒÙŠÙ„Ùˆ Ø¬Ø±Ø§Ù…Ø§Ù†", (* 2000))
  , ( "ÙƒÙŠÙ„Ùˆ Ø¬Ø±Ø§Ù…ÙŠÙ†", (* 2000))
  ]

ruleNumeralQuantities :: [Rule]
ruleNumeralQuantities = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (name, regexPattern, u) = Rule
      { name = name
      , pattern = [ numberWith TNumeral.value (> 0), regex regexPattern ]
      , prod = \tokens -> case tokens of
        (Token Numeral nd:
         Token RegexMatch (GroupMatch (match:_)):
         _) -> Just . Token Quantity $ quantity u value
          where value = getValue opsMap match $ TNumeral.value nd
        _ -> Nothing
      }

ruleAQuantity :: [Rule]
ruleAQuantity = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (name, regexPattern, u) = Rule
      { name = name
      , pattern = [ regex regexPattern ]
      , prod = \tokens -> case tokens of
        (Token RegexMatch (GroupMatch (match:_)):
         _) -> Just . Token Quantity $ quantity u $ getValue opsMap match 1
        _ -> Nothing
      }

ruleQuantityOfProduct :: Rule
ruleQuantityOfProduct = Rule
  { name = "<quantity> of product"
  , pattern =
    [ dimension Quantity
    , regex "Ù…Ù† ([Ø¡-ÙŠ]+)"
    ]
  , prod = \tokens -> case tokens of
    (Token Quantity qd:Token RegexMatch (GroupMatch (product:_)):_) ->
      Just . Token Quantity $ withProduct product qd
    _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleQuantityOfProduct
  ]
  ++ ruleNumeralQuantities
  ++ ruleAQuantity


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.EN.Corpus
  ( corpus
  , latentCorpus
  ) where

import Prelude
import Data.String

import Duckling.Quantity.Types
import Duckling.Resolve (Options(..))
import Duckling.Testing.Types

corpus :: Corpus
corpus = (testContext, testOptions, allExamples)

latentCorpus :: Corpus
latentCorpus = (testContext, testOptions {withLatent = True}, latentExamples)
  where
    latentExamples = concat
      [
      examples (simple Unnamed 4 Nothing)
                [ "around 4"
                , "four"
                , "~ four"
                ]
      ,
      examples (simple Unnamed 38.5 Nothing)
                [ "about 38.5"
                ]
      ]

allExamples :: [Example]
allExamples = concat
  [ examples (simple Pound 2 (Just "meat"))
             [ "two pounds of meat"
             ]
  , examples (simple Gram 2 Nothing)
             [ "2 grams"
             , "0.002 kg"
             , "2 g."
             , "2 gs"
             , "2/1000 kilograms"
             , "2000 milligrams"
             , "2,000 milligrams"
             ]
  , examples (simple Gram 1000 Nothing)
             [ "a kilogram"
             , "a kg"
             , "1 k.g."
             , "1 k.gs"
             , "1000 gs"
             ]
  , examples (simple Pound 1 Nothing)
             [ "a Pound"
             , "1 lb"
             , "a lb"
             ]
  , examples (simple Ounce 2 Nothing)
             [ "2 ounces"
             , "2oz"
             ]
  , examples (simple Cup 3 (Just "sugar"))
             [ "3 Cups of sugar"
             , "3 Cups of SugAr"
             ]
  , examples (simple Cup 0.75 Nothing)
             [ "3/4 cup"
             , "0.75 cup"
             , ".75 cups"
             ]
  , examples (simple Gram 500 (Just "strawberries"))
             [ "500 grams of strawberries"
             , "500g of strawberries"
             , "0.5 kilograms of strawberries"
             , "0.5 kg of strawberries"
             , "500000mg of strawberries"
             ]
  , examples (between Gram (100,1000) (Just "strawberries"))
              [ "100-1000 gram of strawberries"
              , "between 100 and 1000 grams of strawberries"
              , "from 100 to 1000 g of strawberries"
              , "100 - 1000 g of strawberries"
              ]
  , examples (between Gram (2,7) Nothing)
              [ "around 2 -7 g"
              , "~2-7 grams"
              , "from 2 to 7 g"
              , "between 2.0 g and about 7.0 g"
              , "between 0.002 kg and about 0.007 kg"
              , "2 - ~7 grams"
              ]
  , examples (under Pound 6 (Just "meat"))
              [ "less than six pounds of meat"
              , "no more than 6 lbs of meat"
              , "below 6.0 pounds of meat"
              , "at most six pounds of meat"
              ]
  , examples (above Cup 2 Nothing)
              [ "exceeding 2 Cups"
              , "at least two Cups"
              , "over 2 Cups"
              , "more than 2 Cups"
              ]
  , examples (above Ounce 4 (Just "chocolate"))
              [ "exceeding 4 oz of chocolate"
              , "at least 4.0 oz of chocolate"
              , "over four ounces of chocolate"
              , "more than four ounces of chocolate"
              ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Duckling.Quantity.EN.Rules
  ( rules
  ) where

import Data.HashMap.Strict (HashMap)
import Data.String
import Data.Text (Text)
import Prelude
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text

import Duckling.Dimensions.Types
import Duckling.Numeral.Helpers
import Duckling.Quantity.Helpers
import Duckling.Regex.Types (GroupMatch(..))
import Duckling.Types
import Duckling.Numeral.Types (NumeralData(..))
import Duckling.Quantity.Types (QuantityData(..))
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

quantities :: [(Text, String, TQuantity.Unit)]
quantities =
  [ ("<quantity> cups", "(cups?)", TQuantity.Cup)
  , ("<quantity> grams", "(((m(illi)?[.]?)|(k(ilo)?)[.]?)?g(ram)?s?[.]?)[.]?", TQuantity.Gram)
  , ("<quantity> lb", "((lb|pound)s?)", TQuantity.Pound)
  , ("<quantity> oz", "((ounces?)|oz)", TQuantity.Ounce)
  ]

opsMap :: HashMap Text (Double -> Double)
opsMap = HashMap.fromList
  [ ( "milligram" , (/ 1000))
  , ( "milligrams", (/ 1000))
  , ( "mg"        , (/ 1000))
  , ( "mgs"       , (/ 1000))
  , ( "m.g"       , (/ 1000))
  , ( "m.gs"      , (/ 1000))
  , ( "m.g."      , (/ 1000))
  , ( "m.g.s"     , (/ 1000))
  , ( "kilogram"  , (* 1000))
  , ( "kilograms" , (* 1000))
  , ( "kg"        , (* 1000))
  , ( "kgs"       , (* 1000))
  , ( "k.g"       , (* 1000))
  , ( "k.gs"      , (* 1000))
  , ( "k.g."      , (* 1000))
  , ( "k.g.s"     , (* 1000))
  ]

ruleNumeralQuantities :: [Rule]
ruleNumeralQuantities = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (name, regexPattern, u) = Rule
      { name = name
      , pattern = [Predicate isPositive, regex regexPattern]
      , prod = \case
        (Token Numeral nd:
         Token RegexMatch (GroupMatch (match:_)):
         _) -> Just . Token Quantity $ quantity u value
          where value = getValue opsMap match $ TNumeral.value nd
        _ -> Nothing
      }

ruleAQuantity :: [Rule]
ruleAQuantity = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (name, regexPattern, u) = Rule
      { name = name
      , pattern = [ regex ("an? " ++ regexPattern) ]
      , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):
         _) -> Just . Token Quantity $ quantity u $ getValue opsMap match 1
        _ -> Nothing
      }

ruleQuantityOfProduct :: Rule
ruleQuantityOfProduct = Rule
  { name = "<quantity> of product"
  , pattern =
    [ dimension Quantity
    , regex "of (\\w+)"
    ]
  , prod = \case
    (Token Quantity qd:Token RegexMatch (GroupMatch (product:_)):_) ->
      Just . Token Quantity $ withProduct (Text.toLower product) qd
    _ -> Nothing
  }

rulePrecision :: Rule
rulePrecision = Rule
    { name = "about|exactly <quantity>"
    , pattern =
      [ regex "\\~|exactly|precisely|about|approx(\\.|imately)?|close to|near( to)?|around|almost"
      , dimension Quantity
      ]
      , prod = \case
        (_:token:_) -> Just token
        _ -> Nothing
  }

ruleIntervalBetweenNumeral :: Rule
ruleIntervalBetweenNumeral = Rule
    { name = "between|from <numeral> and|to <quantity>"
    , pattern =
      [ regex "between|from"
      , Predicate isPositive
      , regex "to|and"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Numeral NumeralData{TNumeral.value = from}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to ->
          Just . Token Quantity . withInterval (from, to) $ unitOnly u
        _ -> Nothing
    }

ruleIntervalBetween :: Rule
ruleIntervalBetween = Rule
    { name = "between|from <quantity> to|and <quantity>"
    , pattern =
      [ regex "between|from"
      , Predicate isSimpleQuantity
      , regex "and|to"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u1
                                    , TQuantity.aproduct = Nothing}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u2
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to && u1 == u2 ->
          Just . Token Quantity . withInterval (from, to) $ unitOnly u1
        _ -> Nothing
    }

ruleIntervalNumeralDash :: Rule
ruleIntervalNumeralDash = Rule
    { name = "<numeral> - <quantity>"
    , pattern =
      [ Predicate isPositive
      , regex "\\-"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = from}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to ->
           Just . Token Quantity . withInterval (from, to) $ unitOnly u
        _ -> Nothing
    }

ruleIntervalDash :: Rule
ruleIntervalDash = Rule
    { name = "<quantity> - <quantity>"
    , pattern =
      [ Predicate isSimpleQuantity
      , regex "\\-"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u1
                                    , TQuantity.aproduct = Nothing}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u2
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to && u1 == u2 ->
          Just . Token Quantity . withInterval (from, to) $ unitOnly u1
        _ -> Nothing
    }

ruleIntervalMax :: Rule
ruleIntervalMax = Rule
    { name = "under/below/less/lower/at most/no more than <dist>"
    , pattern =
      [ regex "under|below|at most|(less|lower|not? more) than"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) -> Just . Token Quantity . withMax to $ unitOnly u
        _ -> Nothing
    }

ruleIntervalMin :: Rule
ruleIntervalMin = Rule
  { name = "over/above/exceeding/beyond/at least/more than <quantity>"
  , pattern =
      [ regex "over|above|exceeding|beyond|at least|(more|larger|bigger|heavier) than"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) -> Just . Token Quantity . withMin from $ unitOnly u
        _ -> Nothing
    }

ruleQuantityLatent :: Rule
ruleQuantityLatent = Rule
  { name = "<quantity> (latent)"
  , pattern =
    [ Predicate isPositive
    ]
  , prod = \case
      (Token Numeral NumeralData{TNumeral.value = v}: _) ->
        Just $ (Token Quantity . mkLatent) $ valueOnly v
      _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleQuantityOfProduct
  , ruleIntervalMin
  , ruleIntervalMax
  , ruleIntervalBetweenNumeral
  , ruleIntervalBetween
  , ruleIntervalNumeralDash
  , ruleIntervalDash
  , rulePrecision
  , ruleQuantityLatent
  ]
  ++ ruleNumeralQuantities
  ++ ruleAQuantity


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.ES.Corpus
  ( corpus
  ) where

import Prelude
import Data.String

import Duckling.Locale
import Duckling.Quantity.Types
import Duckling.Resolve
import Duckling.Testing.Types

context :: Context
context = testContext { locale = makeLocale ES Nothing }

corpus :: Corpus
corpus = (context, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [ examples (simple Pound 2 (Just "carne"))
             [ "dos libras de carne"
             ]
  , examples (simple Gram 2 Nothing)
             [ "dos gramos"
             , "0,002 kg"
             , "2/1000 kilogramos"
             , "2000 miligramos"
             ]
  , examples (simple Gram 1000 Nothing)
             [ "un kilogramo"
             , "un kg"
             ]
  , examples (simple Pound 1 Nothing)
             [ "una libra"
             , "1 lb"
             , "una lb"
             ]
  , examples (simple Ounce 2 Nothing)
             [ "2 onzas"
             , "2oz"
             ]
  , examples (simple Cup 3 (Just "azucar"))
             [ "tres tazas de azucar"
             , "3 tazas de AzUcAr"
             ]
  , examples (simple Cup 0.75 Nothing)
             [ "3/4 taza"
             , "0,75 taza"
             , ",75 tazas"
             ]
  , examples (simple Gram 500 (Just "fresas"))
             [ "500 gramos de fresas"
             , "500g de fresas"
             , "0,5 kilogramos de fresas"
             , "0,5 kg de fresas"
             , "500000mg de fresas"
             ]
  , examples (under Pound 6 (Just "carne"))
              [ "menos que seis libras de carne"
              , "no mÃ¡s que 6 lbs de carne"
              , "por debajo de 6,0 libras de carne"
              , "a lo sumo seis libras de carne"
              ]
  , examples (above Cup 2 Nothing)
              [ "excesivo 2 tazas"
              , "como mÃnimo dos tazas"
              , "mayor de 2 tazas"
              , "mÃ¡s de 2 tazas"
              ]
  , examples (above Ounce 4 (Just "chocolate"))
              [ "excesivo 4 oz de chocolate"
              , "al menos 4,0 oz de chocolate"
              , "mayor de cuatro onzas de chocolate"
              , "mÃ¡s de cuatro onzas de chocolate"
              ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Duckling.Quantity.ES.Rules
  ( rules
  ) where

import Data.HashMap.Strict (HashMap)
import Data.String
import Data.Text (Text)
import Prelude
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text

import Duckling.Dimensions.Types
import Duckling.Numeral.Helpers
import Duckling.Quantity.Helpers
import Duckling.Regex.Types (GroupMatch(..))
import Duckling.Types
import Duckling.Numeral.Types (NumeralData(..))
import Duckling.Quantity.Types (QuantityData(..))
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

-- Our quantities: (name, regex, quantityType). Please make sure to wrap
-- each regex in parentheses, because we extend these regexes later with
-- articles (un/una).
quantities :: [(Text, String, TQuantity.Unit)]
quantities =
  [ ("<quantity> bowls", "(bol(es)?|tazÃ³n(es)?|cuencos?|platos? (soperos?)|(hondos?))", TQuantity.Bowl)
  , ("<quantity> cups", "(tazas?)", TQuantity.Cup)
  , ("<quantity> dishes", "(platos?|fuentes?)", TQuantity.Dish)
  , ("<quantity> grams", "(((m(ili)?)|(k(ilo)?))?g(ramo)?s?)", TQuantity.Gram)
  , ("<quantity> ounces", "((onzas?)|oz)", TQuantity.Ounce)
  , ("<quantity> pints", "(pintas?)", TQuantity.Pint)
  , ("<quantity> pounds", "((lb|libra)s?)", TQuantity.Pound)
  , ("<quantity> quarts", "(cuartos? de galÃ³n)", TQuantity.Quart)
  , ("<quantity> tablespoons", "(cucharadas? (grande)?)", TQuantity.Tablespoon)
  , ("<quantity> teaspoons", "(cucharaditas?)", TQuantity.Teaspoon)
  ]

opsMap :: HashMap Text (Double -> Double)
opsMap = HashMap.fromList
  [ ( "miligram"   , (/ 1000))
  , ( "miligramos" , (/ 1000))
  , ( "mg"         , (/ 1000))
  , ( "mgs"        , (/ 1000))
  , ( "kilogramo"  , (* 1000))
  , ( "kilogramos" , (* 1000))
  , ( "kg"         , (* 1000))
  , ( "kgs"        , (* 1000))
  ]

ruleNumeralQuantities :: [Rule]
ruleNumeralQuantities = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (nm, regexPattern, u) = Rule
      { name = nm
      , pattern = [Predicate isPositive, regex regexPattern]
      , prod = \case
        (Token Numeral nd:
         Token RegexMatch (GroupMatch (match:_)):
         _) -> Just $ Token Quantity $ quantity u val
          where val = getValue opsMap match $ TNumeral.value nd
        _ -> Nothing
      }

-- Quantities prefixed by "un" or "una"
ruleAQuantity :: [Rule]
ruleAQuantity = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (nm, regexPattern, u) = Rule
      { name = nm
      , pattern = [ regex ("una? " ++ regexPattern) ]
      , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):
         _) -> Just $ Token Quantity $ quantity u $ getValue opsMap match 1
        _ -> Nothing
      }

ruleQuantityOfProduct :: Rule
ruleQuantityOfProduct = Rule
  { name = "<quantity> de producto"
  , pattern =
    [ dimension Quantity
    , regex "de (\\w+)"
    ]
  , prod = \case
    (Token Quantity qd:Token RegexMatch (GroupMatch (prdct:_)):_) ->
      Just $ Token Quantity $ withProduct (Text.toLower prdct) qd
    _ -> Nothing
  }

rulePrecision :: Rule
rulePrecision = Rule
    { name = "about|exactly <quantity>"
    , pattern =
      [ regex "exactamente|precisamente|a?cerca( de)?|aproximadamente|casi"
      , dimension Quantity
      ]
      , prod = \case
        (_:tkn:_) -> Just tkn
        _ -> Nothing
  }

ruleIntervalBetweenNumeral :: Rule
ruleIntervalBetweenNumeral = Rule
    { name = "between|from <numeral> and|to <quantity>"
    , pattern =
      [ regex "entre|de"
      , Predicate isPositive
      , regex "a|y"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Numeral NumeralData{TNumeral.value = from}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to ->
          Just $ Token Quantity $ withInterval (from, to) $ unitOnly u
        _ -> Nothing
    }

ruleIntervalBetween :: Rule
ruleIntervalBetween = Rule
    { name = "between|from <quantity> to|and <quantity>"
    , pattern =
      [ regex "entre/de"
      , Predicate isSimpleQuantity
      , regex "a/y"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u1
                                    , TQuantity.aproduct = Nothing}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u2
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to && u1 == u2 ->
          Just $ Token Quantity $ withInterval (from, to) $ unitOnly u1
        _ -> Nothing
    }

ruleIntervalNumeralDash :: Rule
ruleIntervalNumeralDash = Rule
    { name = "<numeral> - <quantity>"
    , pattern =
      [ Predicate isPositive
      , regex "\\-"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = from}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to ->
           Just $ Token Quantity $ withInterval (from, to) $ unitOnly u
        _ -> Nothing
    }

ruleIntervalDash :: Rule
ruleIntervalDash = Rule
    { name = "<quantity> - <quantity>"
    , pattern =
      [ Predicate isSimpleQuantity
      , regex "\\-"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u1
                                    , TQuantity.aproduct = Nothing}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u2
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to && u1 == u2 ->
          Just $ Token Quantity $ withInterval (from, to) $ unitOnly u1
        _ -> Nothing
    }

ruleIntervalMax :: Rule
ruleIntervalMax = Rule
    { name = "under/below/less/lower/at most/no more than <dist>"
    , pattern =
      [ regex "no m(Ã¡|a)s que|menos de|por debajo de|como mucho|como m(Ã¡|a)xim(o|a)|a lo sumo|menos (que|de)"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) -> Just $ Token Quantity $ withMax to $ unitOnly u
        _ -> Nothing
    }

ruleIntervalMin :: Rule
ruleIntervalMin = Rule
  { name = "over/above/exceeding/beyond/at least/more than <quantity>"
  , pattern =
      [ regex "(?<!no )m(Ã¡|a)s( grande| pesado)? (de|que)|mayor de|por encima de|excesivo|fuera de|por lo menos|como m(Ã|i)nim(o|a)|al menos"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) -> Just $ Token Quantity $ withMin from $ unitOnly u
        _ -> Nothing
    }

ruleQuantityLatent :: Rule
ruleQuantityLatent = Rule
  { name = "<quantity> (latent)"
  , pattern =
    [ Predicate isPositive
    ]
  , prod = \case
      (Token Numeral NumeralData{TNumeral.value = v}: _) ->
        Just $ Token Quantity $ mkLatent $ valueOnly v
      _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleQuantityOfProduct
  , ruleIntervalMin
  , ruleIntervalMax
  , ruleIntervalBetweenNumeral
  , ruleIntervalBetween
  , ruleIntervalNumeralDash
  , ruleIntervalDash
  , rulePrecision
  , ruleQuantityLatent
  ]
  ++ ruleNumeralQuantities
  ++ ruleAQuantity


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.FR.Corpus
  ( corpus ) where

import Prelude
import Data.String

import Duckling.Locale
import Duckling.Quantity.Types
import Duckling.Resolve
import Duckling.Testing.Types

corpus :: Corpus
corpus = (testContext {locale = makeLocale FR Nothing}, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [ examples (simple Cup 2 (Just "cafÃ©"))
             [ "2 tasses de cafÃ©"
             ]
  , examples (simple Cup 1 Nothing)
             [ "une Tasse"
             ]
  , examples (simple Tablespoon 3 (Just "sucre"))
             [ "3 CuillÃ¨res Ã  soupe de sucre"
             ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.FR.Rules
  ( rules
  ) where

import Data.String
import Prelude
import qualified Data.Text as Text

import Duckling.Dimensions.Types
import Duckling.Quantity.Helpers
import Duckling.Numeral.Types (NumeralData (..))
import Duckling.Regex.Types
import Duckling.Types
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

ruleNumeralUnits :: Rule
ruleNumeralUnits = Rule
  { name = "<number> <units>"
  , pattern =
    [ dimension Numeral
    , regex "(tasses?|cuill?(e|Ã¨)res? (a|Ã ) soupe?)"
    ]
  , prod = \case
      (Token Numeral NumeralData {TNumeral.value = v}:
       Token RegexMatch (GroupMatch (match:_)):
       _) -> case Text.toLower match of
         "tasse"  -> Just . Token Quantity $ quantity TQuantity.Cup v
         "tasses" -> Just . Token Quantity $ quantity TQuantity.Cup v
         _        -> Just . Token Quantity $ quantity TQuantity.Tablespoon v
      _ -> Nothing
  }

ruleQuantityOfProduct :: Rule
ruleQuantityOfProduct = Rule
  { name = "<quantity> of product"
  , pattern =
    [ dimension Quantity
    , regex "de (caf(e|Ã©)|sucre)"
    ]
  , prod = \case
      (Token Quantity qd:
       Token RegexMatch (GroupMatch (match:_)):
       _) -> Just . Token Quantity $ withProduct match qd
      _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleNumeralUnits
  , ruleQuantityOfProduct
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.HR.Corpus
  ( corpus ) where

import Prelude
import Data.String

import Duckling.Locale
import Duckling.Quantity.Types
import Duckling.Resolve
import Duckling.Testing.Types

corpus :: Corpus
corpus = (testContext {locale = makeLocale HR Nothing}, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [ examples (simple Gram 2000 (Just "meso"))
             [ "dvije kile mesa"
             , "dva kilograma mesa"
             ]
  , examples (simple Gram 1000 Nothing)
             [ "1 kilograma"
             ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.HR.Rules
  ( rules ) where

import Data.String
import Prelude
import qualified Data.Text as Text

import Duckling.Dimensions.Types
import Duckling.Numeral.Types (NumeralData (..))
import Duckling.Quantity.Helpers
import Duckling.Regex.Types
import Duckling.Types
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

ruleNumberUnits :: Rule
ruleNumberUnits = Rule
  { name = "<number> <units>"
  , pattern =
    [ dimension Numeral
    , regex "k(il(o|e|a))?(g(rama?)?)?"
    ]
  , prod = \tokens -> case tokens of
      (Token Numeral NumeralData {TNumeral.value = v}:_) ->
        Just . Token Quantity $ quantity TQuantity.Gram (1000 * v)
      _ -> Nothing
  }

ruleQuantityProduct :: Rule
ruleQuantityProduct = Rule
  { name = "<quantity> product"
  , pattern =
    [ dimension Quantity
    , regex "(mes(o|a)|soli?)"
    ]
  , prod = \tokens -> case tokens of
      (Token Quantity qd:
       Token RegexMatch (GroupMatch (match:_)):
       _) -> case Text.toLower match of
         "meso" -> Just . Token Quantity $ withProduct "meso" qd
         "mesa" -> Just . Token Quantity $ withProduct "meso" qd
         "sol" -> Just . Token Quantity $ withProduct "sol" qd
         "soli" -> Just . Token Quantity $ withProduct "sol" qd
         _      -> Nothing
      _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleNumberUnits
  , ruleQuantityProduct
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.MN.Corpus
  ( corpus
  ) where

import Data.String
import Prelude

import Duckling.Locale
import Duckling.Resolve
import Duckling.Quantity.Types
import Duckling.Testing.Types

corpus :: Corpus
corpus = (testContext {locale = makeLocale MN Nothing}, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [ examples (simple Pound 2 Nothing)
             [ "2 Ñ„ÑƒÐ½Ñ‚"
             ]
  , examples (simple Gram 2 Nothing)
             [ "2 Ð³Ñ€Ð°Ð¼Ð¼"
             , "Ñ…Ð¾Ñ‘Ñ€ Ð³Ñ€Ð°Ð¼Ð¼"
             , "2000 Ð¼Ð¸Ð»Ð»Ð¸Ð³Ñ€Ð°Ð¼Ð¼"
             , "2000 Ð¼Ð³"
             ]
  , examples (simple Gram 1000 Nothing)
             [ "ÐºÐ¸Ð»Ð¾Ð³Ñ€Ð°Ð¼Ð¼"
             , "ÐºÐ³"
             ]
  , examples (simple Gram 2000 Nothing)
             [ "2 ÐºÐ¸Ð»Ð¾Ð³Ñ€Ð°Ð¼Ð¼"
             , "2 ÐºÐ³"
             , "2000 Ð³Ñ€Ð°Ð¼Ð¼"
             ]
  , examples (simple Pound 1 Nothing)
             [ "Ñ„ÑƒÐ½Ñ‚"
             , "1 Ñ„ÑƒÐ½Ñ‚"
             ]
  , examples (simple Ounce 2 Nothing)
             [ "2 ÑƒÐ½Ñ†"
             ]
  , examples (simple Gram 500 Nothing)
             [ "500 Ð³Ñ€Ð°Ð¼Ð¼"
             , "500Ð³"
             , "500 Ð³"
             , "0.5 ÐºÐ³"
             ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.MN.Rules
  ( rules
  ) where

import Data.String
import Data.Text (Text)
import Prelude
import qualified Data.Text as Text

import Duckling.Dimensions.Types
import Duckling.Numeral.Helpers
import Duckling.Quantity.Helpers
import Duckling.Regex.Types
import Duckling.Types
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

quantities :: [(Text, String, TQuantity.Unit, Double -> Double)]
quantities =
  [ ("<quantity> milligrams", "(Ð¼Ð³|Ð¼Ð¸Ð»Ð»Ð¸Ð³Ñ€Ð°Ð¼Ð¼)", TQuantity.Gram, (/ 1000))
  , ("<quantity> grams", "Ð³(Ñ€Ð°Ð¼Ð¼?)?", TQuantity.Gram, id)
  , ("<quantity> kilograms", "(ÐºÐ³|ÐºÐ¸Ð»Ð¾Ð³Ñ€Ð°Ð¼Ð¼?)", TQuantity.Gram, (* 1000))
  , ("<quantity> lb", "Ñ„ÑƒÐ½Ñ‚", TQuantity.Pound, id)
  , ("<quantity> oz", "ÑƒÐ½Ñ†", TQuantity.Ounce, id)
  ]

ruleNumeralQuantities :: [Rule]
ruleNumeralQuantities = map go quantities
  where
    go :: (Text, String, TQuantity.Unit, Double -> Double) -> Rule
    go (name, regexPattern, u, convert) = Rule
      { name = name
      , pattern =
        [ numberWith TNumeral.value (> 0), regex regexPattern ]
      , prod = \tokens -> case tokens of
        (Token Numeral nd:_) ->
          Just . Token Quantity . quantity u . convert $ TNumeral.value nd
        _ -> Nothing
      }

ruleAQuantity :: [Rule]
ruleAQuantity = map go quantities
  where
    go :: (Text, String, TQuantity.Unit, Double -> Double) -> Rule
    go (name, regexPattern, u, convert) = Rule
      { name = name
      , pattern = [ regex regexPattern ]
      , prod = \_ -> Just . Token Quantity . quantity u $ convert 1
      }

rules :: [Rule]
rules = ruleNumeralQuantities ++ ruleAQuantity


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.NL.Corpus
  ( corpus
  ) where

import Data.String
import Prelude

import Duckling.Locale
import Duckling.Quantity.Types
import Duckling.Resolve
import Duckling.Testing.Types

context :: Context
context = testContext {locale = makeLocale NL Nothing}

corpus :: Corpus
corpus = (context, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [ examples (simple Gram 2 Nothing)
             [ "2 gram"
             , "0,002 kg"
             , "0,002 kilo"
             , "2/1000 kilogram"
             , "2000 milligram"
             ]
  , examples (simple Gram 1000 Nothing)
             [ "1 kg"
             , "1,0 kg"
             , "1 kilogram"
             , "1 kilo"
             , "1000 gram"
             , "1000 g"
             , "1000 gr"
             , "duizend gram"
             , "duizend gr"
             , "2,0 pond"
             , "10 ons"
             , "1000000 mg"
             , "1000000 milligram"
             ]
  , examples (simple Cup 1 (Just "suiker"))
             [ "1 kopje suiker"
             ]
  , examples (simple Cup 3 (Just "suiker"))
             [ "3 kopjes suiker"
             ]
  , examples (simple Cup 0.75 Nothing)
             [ "3/4 kopje"
             , "0,75 kopje"
             , ",75 kopje"
             ]
  , examples (simple Gram 500 (Just "aardbeien"))
             [ "500 gram aardbeien"
             , "500g aardbeien"
             , "0,5 kilogram aardbeien"
             , "0,5 kg aardbeien"
             , "5 ons aardbeien"
             , "1 pond aardbeien"
             , "500000mg aardbeien"
             ]
  , examples (between Gram (100,1000) (Just "aardbeien"))
              [ "100-1000 gram aardbeien"
              , "tussen 100 en 1000 gram aardbeien"
              , "van 100 tot 1000 g aardbeien"
              , "tussen 1 ons en 10 ons aardbeien"
              , "100 - 1000 g aardbeien"
              ]
  , examples (between Gram (2,7) Nothing)
              [ "~2-7 gram"
              , "van 2 tot 7 g"
              , "tussen 2,0 g en ongeveer 7,0 g"
              , "tussen 0,002 kg en ongeveer 0,007 kg"
              , "2 - ~7 gram"
              ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Duckling.Quantity.NL.Rules
  ( rules
  ) where

import Data.HashMap.Strict (HashMap)
import Data.String
import Data.Text (Text)
import Prelude
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text

import Duckling.Dimensions.Types
import Duckling.Numeral.Helpers
import Duckling.Quantity.Helpers
import Duckling.Regex.Types (GroupMatch (..))
import Duckling.Types
import Duckling.Numeral.Types (NumeralData (..))
import Duckling.Quantity.Types (QuantityData(..))
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

quantities :: [(Text, String, TQuantity.Unit)]
quantities =
  [ ("<quantity> kopje",        "(kopjes?)", TQuantity.Cup)
  , ("<quantity> grams",        "(g((r)?(am)?)?)", TQuantity.Gram)
  , ("<quantity> milligrams",   "((m(illi)?)(g(ram)?))", TQuantity.Gram)
  , ("<quantity> kilograms",    "((k(ilo)?)(g(ram)?)?)", TQuantity.Gram)
  , ("<quantity> pond",         "(pond(je(s)?)?)", TQuantity.Gram)
  , ("<quantity> ons",          "(ons(je(s)?)?)", TQuantity.Gram)
  ]

opsMap :: HashMap Text (Double -> Double)
opsMap = HashMap.fromList
  [ ( "milligram"   , (/ 1000))
  , ( "mg"          , (/ 1000))
  , ( "kilo"        , (* 1000))
  , ( "kilogram"    , (* 1000))
  , ( "kg"          , (* 1000))
  , ( "pond"        , (* 500))
  , ( "ons"         , (* 100))
  ]

ruleNumeralQuantities :: [Rule]
ruleNumeralQuantities = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (name, regexPattern, u) = Rule
      { name = name
      , pattern = [Predicate isPositive, regex regexPattern]
      , prod = \case
        (Token Numeral nd:
         Token RegexMatch (GroupMatch (match:_)):
         _) -> Just . Token Quantity $ quantity u value
          where value = getValue opsMap match $ TNumeral.value nd
        _ -> Nothing
      }

ruleAQuantity :: [Rule]
ruleAQuantity = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (name, regexPattern, u) = Rule
      { name = name
      , pattern = [ regex ("een? " ++ regexPattern) ]
      , prod = \case
        (Token RegexMatch (GroupMatch (match:_)):
         _) -> Just . Token Quantity $ quantity u $ getValue opsMap match 1
        _ -> Nothing
      }

ruleQuantityOfProduct :: Rule
ruleQuantityOfProduct = Rule
  { name = "<quantity> product"
  , pattern =
    [ dimension Quantity
    , regex "(\\w+)"
    ]
  , prod = \case
    (Token Quantity qd:Token RegexMatch (GroupMatch (product:_)):_) ->
      Just . Token Quantity $ withProduct (Text.toLower product) qd
    _ -> Nothing
  }

rulePrecision :: Rule
rulePrecision = Rule
    { name = "ongeveer|plm|plusminus <quantity>"
    , pattern =
      [ regex "\\~|precies|exact|ongeveer|bijna|ongeveer"
      , dimension Quantity
      ]
      , prod = \case
        (_:token:_) -> Just token
        _ -> Nothing
  }

ruleIntervalBetweenNumeral :: Rule
ruleIntervalBetweenNumeral = Rule
    { name = "tussen|van <numeral> en|tot <quantity>"
    , pattern =
      [ regex "tussen|van"
      , Predicate isPositive
      , regex "tot|en"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Numeral NumeralData{TNumeral.value = from}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to ->
          Just . Token Quantity . withInterval (from, to) $ unitOnly u
        _ -> Nothing
    }

ruleIntervalBetween :: Rule
ruleIntervalBetween = Rule
    { name = "rond|tussen|van <quantity> tot|en <quantity>"
    , pattern =
      [ regex "tussen|van"
      , Predicate isSimpleQuantity
      , regex "en|tot"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u1
                                    , TQuantity.aproduct = Nothing}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u2
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to && u1 == u2 ->
          Just . Token Quantity . withInterval (from, to) $ unitOnly u1
        _ -> Nothing
    }

ruleIntervalNumeralDash :: Rule
ruleIntervalNumeralDash = Rule
    { name = "<numeral> - <quantity>"
    , pattern =
      [ Predicate isPositive
      , regex "\\-"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (Token Numeral NumeralData{TNumeral.value = from}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to ->
           Just . Token Quantity . withInterval (from, to) $ unitOnly u
        _ -> Nothing
    }

ruleIntervalDash :: Rule
ruleIntervalDash = Rule
    { name = "<quantity> - <quantity>"
    , pattern =
      [ Predicate isSimpleQuantity
      , regex "\\-"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u1
                                    , TQuantity.aproduct = Nothing}:
         _:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u2
                                    , TQuantity.aproduct = Nothing}:
         _) | from < to && u1 == u2 ->
          Just . Token Quantity . withInterval (from, to) $ unitOnly u1
        _ -> Nothing
    }

ruleIntervalMax :: Rule
ruleIntervalMax = Rule
    { name = "minder dan/hoogstens/op zijn hoogst/maximaal/hooguit <quantity>"
    , pattern =
      [ regex "minder dan|hoogstens|hooguit|maximaal|op zijn hoogst"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just to
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) -> Just . Token Quantity . withMax to $ unitOnly u
        _ -> Nothing
    }

ruleIntervalMin :: Rule
ruleIntervalMin = Rule
  { name = "meer dan/minstens/op zijn minst <quantity>"
  , pattern =
      [ regex "meer dan|minstens|minimaal|op zijn minst|minder dan"
      , Predicate isSimpleQuantity
      ]
    , prod = \case
        (_:
         Token Quantity QuantityData{TQuantity.value = Just from
                                    , TQuantity.unit = Just u
                                    , TQuantity.aproduct = Nothing}:
         _) -> Just . Token Quantity . withMin from $ unitOnly u
        _ -> Nothing
    }

rules :: [Rule]
rules =
  [ ruleQuantityOfProduct
  , ruleIntervalMin
  , ruleIntervalMax
  , ruleIntervalBetweenNumeral
  , ruleIntervalBetween
  , ruleIntervalNumeralDash
  , ruleIntervalDash
  , rulePrecision
  ]
  ++ ruleNumeralQuantities
  ++ ruleAQuantity


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.PT.Corpus
  ( corpus ) where

import Prelude
import Data.String

import Duckling.Locale
import Duckling.Quantity.Types
import Duckling.Resolve
import Duckling.Testing.Types

corpus :: Corpus
corpus = (testContext {locale = makeLocale PT Nothing}, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [  examples (simple Pound 2 (Just "carne"))
             [ "duas libras de carne"
             ]
  , examples (simple Gram 2 Nothing)
             [ "2 gramas"
             , "0,002 kg"
             , "2 g"
             , "2/1000 quilogramas"
             , "2/1000 quilos"
             , "2000 miligramas"
             , "2000 miligramas"
             ]
  , examples (simple Gram 1000 Nothing)
             [ "um quilograma"
             , "um quilo"
             , "um kg"
             , "1 kg"
             , "1000 g"
             ]
  , examples (simple Pound 1 Nothing)
             [ "uma Libra"
             , "1 lb"
             , "uma lb"
             ]
  , examples (simple Cup 3 (Just "acucar"))
             [ "3 copos de acucar"
             , "3 copos de AcucAr"
             ]
  , examples (simple Cup 0.75 Nothing)
             [ "3/4 copo"
             , "0,75 copo"
             ]
  , examples (simple Gram 500 (Just "morangos"))
             [ "500 gramas de morangos"
             , "500g de morangos"
             , "0,5 quilos de morangos"
             , "0,5 kg de morangos"
             , "500000mg de morangos"
             ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Duckling.Quantity.PT.Rules
  ( rules ) where

import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text
import Prelude
import Data.String
import Data.Text (Text)

import Duckling.Dimensions.Types
import Duckling.Numeral.Types (NumeralData (..))
import qualified Duckling.Numeral.Types as TNumeral
import Duckling.Numeral.Helpers
import Duckling.Quantity.Helpers
import qualified Duckling.Quantity.Types as TQuantity
import Duckling.Regex.Types
import Duckling.Types

quantities :: [(Text, String, TQuantity.Unit)]
quantities =
  [ ("<quantity> copos", "(copos?)", TQuantity.Cup)
  , ("<quantity> gramas", "((((mili)|(quilo))?(grama)s?)|(quilos?)|((m|k)?g))", TQuantity.Gram)
  , ("<quantity> libras", "((lb|libra)s?)", TQuantity.Pound)
  ]

opsMap :: HashMap Text (Double -> Double)
opsMap = HashMap.fromList
  [ ( "miligrama"  , (/ 1000))
  , ( "miligramas" , (/ 1000))
  , ( "mg"         , (/ 1000))
  , ( "mgs"        , (/ 1000))
  , ( "quilograma" , (* 1000))
  , ( "quilogramas", (* 1000))
  , ( "quilo"      , (* 1000))
  , ( "quilos"     , (* 1000))
  , ( "kg"         , (* 1000))
  , ( "kgs"        , (* 1000))
  ]

ruleNumeralQuantities :: [Rule]
ruleNumeralQuantities = map go quantities
  where
    go :: (Text, String, TQuantity.Unit) -> Rule
    go (name, regexPattern, u) = Rule
      { name = name
      , pattern = [Predicate isPositive, regex regexPattern]
      , prod = \case
          (Token Numeral nd:
           Token RegexMatch (GroupMatch (match:_)):
           _) -> do
            let value = getValue opsMap match $ TNumeral.value nd
            Just $ Token Quantity $ quantity u value
          _ -> Nothing
      }

ruleQuantityOfProduct :: Rule
ruleQuantityOfProduct = Rule
  { name = "<quantity> of product"
  , pattern =
    [ dimension Quantity
    , regex "de (\\w+)"
    ]
  , prod = \case
      (Token Quantity qd:Token RegexMatch (GroupMatch (product:_)):_) ->
        Just $ Token Quantity $ withProduct (Text.toLower product) qd
      _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleQuantityOfProduct ]
  ++ ruleNumeralQuantities


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.RO.Corpus
  ( corpus
  ) where

import Data.String
import Prelude

import Duckling.Locale
import Duckling.Quantity.Types
import Duckling.Resolve
import Duckling.Testing.Types

corpus :: Corpus
corpus = (testContext {locale = makeLocale RO Nothing}, testOptions, allExamples)

allExamples :: [Example]
allExamples = concat
  [ examples (simple Pound 2 (Just "carne"))
             [ "doua livre de carne"
             ]
  , examples (simple Pound 1 Nothing)
             [ "o livrÄƒ"
             ]
  , examples (simple Pound 500 (Just "zahÄƒr"))
             [ "cinci sute livre de zahÄƒr"
             , "cinci sute de livre de zahÄƒr"
             ]
  , examples (simple Pound 21 (Just "mamaliga"))
             [ "douÄƒzeci È™i unu de livre de mamaliga"
             ]
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Duckling.Quantity.RO.Rules
  ( rules
  ) where

import Data.String
import Prelude

import Duckling.Dimensions.Types
import Duckling.Numeral.Helpers (isPositive)
import Duckling.Numeral.Types (NumeralData(..))
import Duckling.Quantity.Helpers
import Duckling.Regex.Types
import Duckling.Types
import qualified Duckling.Numeral.Types as TNumeral
import qualified Duckling.Quantity.Types as TQuantity

ruleNumeralUnits :: Rule
ruleNumeralUnits = Rule
  { name = "<number> <units>"
  , pattern =
    [ Predicate isPositive
    , regex "(de )?livr(a|e|Äƒ)"
    ]
  , prod = \case
      (Token Numeral NumeralData {TNumeral.value = v}:_) ->
        Just . Token Quantity $ quantity TQuantity.Pound v
      _ -> Nothing
  }

ruleQuantityOfProduct :: Rule
ruleQuantityOfProduct = Rule
  { name = "<quantity> of product"
  , pattern =
    [ dimension Quantity
    , regex "de (carne|can[aÄƒ]|zah[aÄƒ]r|mamaliga)"
    ]
  , prod = \case
      (Token Quantity qd:
       Token RegexMatch (GroupMatch (product:_)):
       _) -> Just . Token Quantity $ withProduct product qd
      _ -> Nothing
  }

rules :: [Rule]
rules =
  [ ruleNumeralUnits
  , ruleQuantityOfProduct
  ]


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

module Duckling.Ranking.Classifiers
  ( classifiers
  ) where

import Data.Maybe

import Duckling.Locale
import Duckling.Ranking.Types
import qualified Duckling.Ranking.Classifiers.AF_XX as AF_XXClassifiers
import qualified Duckling.Ranking.Classifiers.AR_XX as AR_XXClassifiers
import qualified Duckling.Ranking.Classifiers.BG_XX as BG_XXClassifiers
import qualified Duckling.Ranking.Classifiers.BN_XX as BN_XXClassifiers
import qualified Duckling.Ranking.Classifiers.CA_XX as CA_XXClassifiers
import qualified Duckling.Ranking.Classifiers.CS_XX as CS_XXClassifiers
import qualified Duckling.Ranking.Classifiers.DA_XX as DA_XXClassifiers
import qualified Duckling.Ranking.Classifiers.DE_XX as DE_XXClassifiers
import qualified Duckling.Ranking.Classifiers.EL_XX as EL_XXClassifiers
import qualified Duckling.Ranking.Classifiers.EN_GB as EN_GBClassifiers
import qualified Duckling.Ranking.Classifiers.EN_US as EN_USClassifiers
import qualified Duckling.Ranking.Classifiers.EN_XX as EN_XXClassifiers
import qualified Duckling.Ranking.Classifiers.ES_XX as ES_XXClassifiers
import qualified Duckling.Ranking.Classifiers.ET_XX as ET_XXClassifiers
import qualified Duckling.Ranking.Classifiers.FI_XX as FI_XXClassifiers
import qualified Duckling.Ranking.Classifiers.FA_XX as FA_XXClassifiers
import qualified Duckling.Ranking.Classifiers.FR_XX as FR_XXClassifiers
import qualified Duckling.Ranking.Classifiers.GA_XX as GA_XXClassifiers
import qualified Duckling.Ranking.Classifiers.HE_XX as HE_XXClassifiers
import qualified Duckling.Ranking.Classifiers.HI_XX as HI_XXClassifiers
import qualified Duckling.Ranking.Classifiers.HR_XX as HR_XXClassifiers
import qualified Duckling.Ranking.Classifiers.HU_XX as HU_XXClassifiers
import qualified Duckling.Ranking.Classifiers.ID_XX as ID_XXClassifiers
import qualified Duckling.Ranking.Classifiers.IS_XX as IS_XXClassifiers
import qualified Duckling.Ranking.Classifiers.IT_XX as IT_XXClassifiers
import qualified Duckling.Ranking.Classifiers.JA_XX as JA_XXClassifiers
import qualified Duckling.Ranking.Classifiers.KA_XX as KA_XXClassifiers
import qualified Duckling.Ranking.Classifiers.KM_XX as KM_XXClassifiers
import qualified Duckling.Ranking.Classifiers.KN_XX as KN_XXClassifiers
import qualified Duckling.Ranking.Classifiers.KO_XX as KO_XXClassifiers
import qualified Duckling.Ranking.Classifiers.LO_XX as LO_XXClassifiers
import qualified Duckling.Ranking.Classifiers.ML_XX as ML_XXClassifiers
import qualified Duckling.Ranking.Classifiers.MN_XX as MN_XXClassifiers
import qualified Duckling.Ranking.Classifiers.MY_XX as MY_XXClassifiers
import qualified Duckling.Ranking.Classifiers.NB_XX as NB_XXClassifiers
import qualified Duckling.Ranking.Classifiers.NE_XX as NE_XXClassifiers
import qualified Duckling.Ranking.Classifiers.NL_XX as NL_XXClassifiers
import qualified Duckling.Ranking.Classifiers.PL_XX as PL_XXClassifiers
import qualified Duckling.Ranking.Classifiers.PT_XX as PT_XXClassifiers
import qualified Duckling.Ranking.Classifiers.RO_XX as RO_XXClassifiers
import qualified Duckling.Ranking.Classifiers.RU_XX as RU_XXClassifiers
import qualified Duckling.Ranking.Classifiers.SK_XX as SK_XXClassifiers
import qualified Duckling.Ranking.Classifiers.SV_XX as SV_XXClassifiers
import qualified Duckling.Ranking.Classifiers.SW_XX as SW_XXClassifiers
import qualified Duckling.Ranking.Classifiers.TA_XX as TA_XXClassifiers
import qualified Duckling.Ranking.Classifiers.TE_XX as TE_XXClassifiers
import qualified Duckling.Ranking.Classifiers.TH_XX as TH_XXClassifiers
import qualified Duckling.Ranking.Classifiers.TR_XX as TR_XXClassifiers
import qualified Duckling.Ranking.Classifiers.UK_XX as UK_XXClassifiers
import qualified Duckling.Ranking.Classifiers.VI_XX as VI_XXClassifiers
import qualified Duckling.Ranking.Classifiers.ZH_XX as ZH_XXClassifiers

classifiers :: Locale -> Classifiers
classifiers (Locale AF _) = AF_XXClassifiers.classifiers
classifiers (Locale AR _) = AR_XXClassifiers.classifiers
classifiers (Locale BG _) = BG_XXClassifiers.classifiers
classifiers (Locale BN _) = BN_XXClassifiers.classifiers
classifiers (Locale CA _) = CA_XXClassifiers.classifiers
classifiers (Locale CS _) = CS_XXClassifiers.classifiers
classifiers (Locale DA _) = DA_XXClassifiers.classifiers
classifiers (Locale DE _) = DE_XXClassifiers.classifiers
classifiers (Locale EL _) = EL_XXClassifiers.classifiers
classifiers (Locale EN (Just GB)) = EN_GBClassifiers.classifiers
classifiers (Locale EN (Just US)) = EN_USClassifiers.classifiers
classifiers (Locale EN _) = EN_XXClassifiers.classifiers
classifiers (Locale ES _) = ES_XXClassifiers.classifiers
classifiers (Locale ET _) = ET_XXClassifiers.classifiers
classifiers (Locale FI _) = FI_XXClassifiers.classifiers
classifiers (Locale FA _) = FA_XXClassifiers.classifiers
classifiers (Locale FR _) = FR_XXClassifiers.classifiers
classifiers (Locale GA _) = GA_XXClassifiers.classifiers
classifiers (Locale HE _) = HE_XXClassifiers.classifiers
classifiers (Locale HI _) = HI_XXClassifiers.classifiers
classifiers (Locale HR _) = HR_XXClassifiers.classifiers
classifiers (Locale HU _) = HU_XXClassifiers.classifiers
classifiers (Locale ID _) = ID_XXClassifiers.classifiers
classifiers (Locale IS _) = IS_XXClassifiers.classifiers
classifiers (Locale IT _) = IT_XXClassifiers.classifiers
classifiers (Locale JA _) = JA_XXClassifiers.classifiers
classifiers (Locale KA _) = KA_XXClassifiers.classifiers
classifiers (Locale KM _) = KM_XXClassifiers.classifiers
classifiers (Locale KN _) = KN_XXClassifiers.classifiers
classifiers (Locale KO _) = KO_XXClassifiers.classifiers
classifiers (Locale LO _) = LO_XXClassifiers.classifiers
classifiers (Locale ML _) = ML_XXClassifiers.classifiers
classifiers (Locale MN _) = MN_XXClassifiers.classifiers
classifiers (Locale MY _) = MY_XXClassifiers.classifiers
classifiers (Locale NB _) = NB_XXClassifiers.classifiers
classifiers (Locale NE _) = NE_XXClassifiers.classifiers
classifiers (Locale NL _) = NL_XXClassifiers.classifiers
classifiers (Locale PL _) = PL_XXClassifiers.classifiers
classifiers (Locale PT _) = PT_XXClassifiers.classifiers
classifiers (Locale RO _) = RO_XXClassifiers.classifiers
classifiers (Locale RU _) = RU_XXClassifiers.classifiers
classifiers (Locale SK _) = SK_XXClassifiers.classifiers
classifiers (Locale SV _) = SV_XXClassifiers.classifiers
classifiers (Locale SW _) = SW_XXClassifiers.classifiers
classifiers (Locale TA _) = TA_XXClassifiers.classifiers
classifiers (Locale TE _) = TE_XXClassifiers.classifiers
classifiers (Locale TH _) = TH_XXClassifiers.classifiers
classifiers (Locale TR _) = TR_XXClassifiers.classifiers
classifiers (Locale UK _) = UK_XXClassifiers.classifiers
classifiers (Locale VI _) = VI_XXClassifiers.classifiers
classifiers (Locale ZH _) = ZH_XXClassifiers.classifiers


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE GADTs #-}
module Duckling.Ranking.Extraction
  ( extractFeatures
  ) where

import qualified Data.HashMap.Strict as HashMap
import Data.Maybe
import qualified Data.Text as Text
import Prelude
import TextShow (showt)

import Duckling.Dimensions.Types
import Duckling.Duration.Types (DurationData (DurationData))
import qualified Duckling.Duration.Types as TDuration
import Duckling.Ranking.Types (BagOfFeatures)
import Duckling.Time.Types (TimeData (TimeData))
import qualified Duckling.Time.Types as TTime
import Duckling.Types (Node, Token(..), rule, children, token)

-- | Feature extraction
-- | Features:
-- | 1) Concatenation of the names of the rules involved in parsing `Node`
-- | 2) Concatenation of the grains for time-like dimensions
extractFeatures :: Node -> BagOfFeatures
extractFeatures node =
  HashMap.fromList $ (featRules, 1) : [ (featGrain, 1) | not (null grains) ]
  where
    featRules = Text.concat $ mapMaybe rule (children node)
    grains = mapMaybe (\x ->
      case token x of
        Token Duration DurationData{TDuration.grain = g} -> Just $ showt g
        Token Time TimeData{TTime.timeGrain = g} -> Just $ showt g
        Token TimeGrain g -> Just $ showt g
        _ -> Nothing
      ) $ children node
    featGrain = Text.concat grains


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoRebindableSyntax #-}

module Duckling.Ranking.Rank
  ( rank
  ) where

import qualified Data.HashMap.Strict as HashMap
import Data.HashSet (HashSet)
import qualified Data.HashSet as HashSet
import Data.Maybe
import qualified Data.Set as Set
import Prelude

import Duckling.Dimensions.Types
import Duckling.Ranking.Extraction
import Duckling.Ranking.Types
import Duckling.Types

-- | computes log likelihood of a class
ll :: BagOfFeatures -> ClassData -> Double
ll feats ClassData{..} =
  prior +
    HashMap.foldrWithKey
      (\feat x res ->
       res + fromIntegral x * HashMap.lookupDefault unseen feat likelihoods)
      0.0
      feats

-- | computes positive class log likelihood
posLL :: Classifier -> BagOfFeatures -> Double
posLL Classifier {..} feats = ll feats okData

score :: Classifiers -> Node -> Double
score classifiers node@Node {rule = Just rule, ..} =
  case HashMap.lookup rule classifiers of
    Just c -> let feats = extractFeatures node
      in posLL c feats + sum (map (score classifiers) children)
    Nothing -> 0.0
score _ Node {rule = Nothing} = 0.0

-- | Return all superior candidates, as defined by the partial ordering
winners :: Ord a => [a] -> [a]
winners xs = filter (\x -> all ((/=) LT . compare x) xs) xs

-- | Return a curated list of tokens
rank
  :: Classifiers
  -> HashSet (Seal Dimension)
  -> [ResolvedToken]
  -> [ResolvedToken]
rank classifiers targets tokens =
  Set.toList
  $ Set.fromList
  $ map (\(Candidate token _ _) -> token)
  $ winners
  $ map makeCandidate tokens
  where
    makeCandidate :: ResolvedToken -> Candidate
    makeCandidate token@Resolved {node = n@Node {token = Token d _}} =
      Candidate token (score classifiers n) $ HashSet.member (Seal d) targets


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree.

{-# LANGUAGE NoRebindableSyntax #-}

module Duckling.Ranking.Types
  ( Feature
  , BagOfFeatures
  , Class
  , Datum
  , Dataset

  , Classifier(..)
  , Classifiers
  , ClassData(..)
  , Candidate(..)

  , infinity
  ) where
import Data.HashMap.Strict (HashMap)
import Data.Text (Text)
import Prelude
import Duckling.Types
  ( Node(..)
  , Range(..)
  , ResolvedToken(..)
  , Token(..)
  , isDimension
  )

-- -----------------------------------------------------------------
-- Aliases

type Feature = Text
type BagOfFeatures = HashMap Feature Int
type Class = Bool
type Datum = (BagOfFeatures, Class)
type Dataset = HashMap Text [Datum]

-- -----------------------------------------------------------------
-- Classification

data Classifier = Classifier
  { okData :: ClassData
  , koData :: ClassData
  }
  deriving (Eq, Show)

type Classifiers = HashMap Text Classifier

data ClassData = ClassData
  { prior :: Double
  , unseen :: Double
  , likelihoods :: HashMap Feature Double
  , n :: Int
  }
  deriving (Eq, Show)

infinity :: Double
infinity = 1 / 0

-- -----------------------------------------------------------------
-- Candidate

-- |A Candidate represents a potential match going into the ranker
data Candidate = Candidate
    ResolvedToken -- ^ The actual resolved token we are considering
    Double -- ^ naive Bayes log-likelihood - sum of LL of all rules used
    Bool -- ^ Does the ResolvedToken's dimension match the caller's request?
  deriving (Eq, Show)

instance Ord Candidate where
  compare (Candidate Resolved{range = Range s1 e1, node = Node{token = Token d1 _}} score1 t1)
          (Candidate Resolved{range = Range s2 e2, node = Node{token = tok2}} score2 t2)
    | isDimension d1 tok2 = case starts of
        EQ -> case ends of
          EQ -> compare score1 score2
          z -> z
        LT -> case ends of
          LT -> EQ
          _ -> GT
        GT -> case ends of
          GT -> EQ
          _ -> LT
    | t1 == t2 = compRange
    | t1 && compRange == GT = GT
    | t2 && compRange == LT = LT
    | otherwise = EQ
      where
        starts = compare s1 s2
        ends = compare e1 e2
        -- a > b if a recovers b
        compRange = case starts of
          EQ -> ends
          LT -> case ends of
            LT -> EQ
            _  -> GT
          GT -> case ends of
            GT -> EQ
            _  -> LT


-- Copyright (c) 2016-present, Facebook, Inc.
-- All rights reserved.
--
-- This source code is licensed under the BSD-style license found in the
-- LICENSE file in the root directory of this source tree. An additional grant
-- of patent rights can be found in the PATENTS file in the same directory.

-----------------------------------------------------------------
-- Auto-generated by regenClassifiers
--
-- DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
--  @generated
-----------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
module Duckling.Ranking.Classifiers.AF_XX (classifiers) where
import Data.String
import Prelude
import qualified Data.HashMap.Strict as HashMap
import Duckling.Ranking.Types

classifiers :: Classifiers
classifiers = HashMap.fromList []