OrderAnalyser.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. # coding: utf8
  2. import collections
  3. from collections import Counter
  4. import six
  5. from jinja2 import Template
  6. from kalliope.core.NeuronParameterLoader import NeuronParameterLoader
  7. from kalliope.core.Models.MatchedSynapse import MatchedSynapse
  8. from kalliope.core.Utils.Utils import Utils
  9. from kalliope.core.ConfigurationManager import SettingLoader
  10. import logging
  11. logging.basicConfig()
  12. logger = logging.getLogger("kalliope")
  13. class OrderAnalyser:
  14. """
  15. This Class is used to get a list of synapses that match a given Spoken order
  16. """
  17. brain = None
  18. settings = None
  19. @classmethod
  20. def __init__(cls):
  21. cls.settings = SettingLoader().settings
  22. @classmethod
  23. def get_matching_synapse(cls, order, brain=None):
  24. """
  25. Return the list of matching synapses from the given order
  26. :param order: The user order
  27. :param brain: The loaded brain
  28. :return: The List of synapses matching the given order
  29. """
  30. cls.brain = brain
  31. logger.debug("[OrderAnalyser] Received order: %s" % order)
  32. if isinstance(order, six.binary_type):
  33. order = order.decode('utf-8')
  34. # We use a namedtuple to associate the synapse and the signal of the synapse
  35. synapse_order_tuple = collections.namedtuple('tuple_synapse_matchingOrder',
  36. ['synapse', 'order'])
  37. list_match_synapse = list()
  38. # if the received order is None we can stop the process immediately
  39. if order is None:
  40. return list_match_synapse
  41. # test each synapse from the brain
  42. for synapse in cls.brain.synapses:
  43. for signal in synapse.signals:
  44. # we are only concerned by synapse with a order type of signal
  45. if signal.name == "order":
  46. # get the type of matching expected, by default "normal"
  47. expected_matching_type = "normal"
  48. signal_order = None
  49. if isinstance(signal.parameters, str):
  50. signal_order = signal.parameters
  51. if isinstance(signal.parameters, dict):
  52. try:
  53. signal_order = signal.parameters["text"]
  54. except KeyError:
  55. logger.debug("[OrderAnalyser] Warning, missing parameter 'text' in order. "
  56. "Order will be skipped")
  57. continue
  58. try:
  59. expected_matching_type = signal.parameters["matching-type"]
  60. except KeyError:
  61. logger.debug("[OrderAnalyser] Warning, missing parameter 'matching-type' in order. "
  62. "Fallback to 'normal'")
  63. if cls.is_order_matching(user_order=order,
  64. signal_order=signal_order,
  65. expected_order_type=expected_matching_type):
  66. # the order match the synapse, we add it to the returned list
  67. logger.debug("Order found! Run synapse name: %s" % synapse.name)
  68. Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
  69. list_match_synapse.append(synapse_order_tuple(synapse=synapse, order=signal_order))
  70. # create a list of MatchedSynapse from the tuple list
  71. list_synapse_to_process = list()
  72. for tuple_el in list_match_synapse:
  73. new_matching_synapse = MatchedSynapse(matched_synapse=tuple_el.synapse,
  74. matched_order=tuple_el.order,
  75. user_order=order)
  76. list_synapse_to_process.append(new_matching_synapse)
  77. return list_synapse_to_process
  78. @staticmethod
  79. def _get_split_order_without_bracket(order):
  80. """
  81. Get an order with bracket inside like: "hello my name is {{ name }}.
  82. return a list of string without bracket like ["hello", "my", "name", "is"]
  83. :param order: sentence to split
  84. :return: list of string without bracket
  85. """
  86. matches = Utils.find_all_matching_brackets(order)
  87. for match in matches:
  88. order = order.replace(match, "")
  89. # then split
  90. split_order = order.split()
  91. return split_order
  92. @classmethod
  93. def is_normal_matching(cls, user_order, signal_order):
  94. """
  95. True if :
  96. - all word in the user_order are present in the signal_order
  97. :param user_order: order from the user
  98. :param signal_order: order in the signal
  99. :return: Boolean
  100. """
  101. logger.debug("[OrderAnalyser] is_normal_matching called with user_order: %s, signal_order: %s" % (user_order,
  102. signal_order))
  103. split_user_order = user_order.split()
  104. split_signal_order_without_brackets = cls._get_split_order_without_bracket(signal_order)
  105. c1, c2 = Counter(split_signal_order_without_brackets), Counter(split_user_order)
  106. for k, n in c1.items():
  107. if n > c2[k]:
  108. return False
  109. return True
  110. @classmethod
  111. def is_strict_matching(cls, user_order, signal_order):
  112. """
  113. True if :
  114. - all word in the user_order are present in the signal_order
  115. - no additional word
  116. :param user_order: order from the user
  117. :param signal_order: order in the signal
  118. :return: Boolean
  119. """
  120. logger.debug("[OrderAnalyser] is_strict_matching called with user_order: %s, signal_order: %s" % (user_order,
  121. signal_order))
  122. if cls.is_normal_matching(user_order=user_order, signal_order=signal_order):
  123. # if the signal order contains bracket, we need to instantiate it with loaded parameters from the user order
  124. if Utils.is_containing_bracket(signal_order):
  125. signal_order = cls._get_instantiated_order_signal_from_user_order(signal_order, user_order)
  126. split_user_order = user_order.split()
  127. split_instantiated_signal = signal_order.split()
  128. if len(split_user_order) == len(split_instantiated_signal):
  129. return True
  130. return False
  131. @classmethod
  132. def is_ordered_strict_matching(cls, user_order, signal_order):
  133. """
  134. True if :
  135. - all word in the user_order are present in the signal_order
  136. - no additional word
  137. - same order as word present in signal_order
  138. :param user_order: order from the user
  139. :param signal_order: order in the signal
  140. :return: Boolean
  141. """
  142. logger.debug(
  143. "[OrderAnalyser] ordered_strict_matching called with user_order: %s, signal_order: %s" % (user_order,
  144. signal_order))
  145. if cls.is_normal_matching(user_order=user_order, signal_order=signal_order) and \
  146. cls.is_strict_matching(user_order=user_order, signal_order=signal_order):
  147. # if the signal order contains bracket, we need to instantiate it with loaded parameters from the user order
  148. if Utils.is_containing_bracket(signal_order):
  149. signal_order = cls._get_instantiated_order_signal_from_user_order(signal_order, user_order)
  150. split_user_order = user_order.split()
  151. split_signal_order = signal_order.split()
  152. return split_user_order == split_signal_order
  153. return False
  154. @classmethod
  155. def is_order_matching(cls, user_order, signal_order, expected_order_type="normal"):
  156. """
  157. return True if the user_order matches the signal_order following the expected_order_type
  158. where "expected_order_type" is in
  159. - normal: normal matching. all words are present in the user_order. this is the default
  160. - strict: only word in the user order match. no more word
  161. - ordered-strict: only word in the user order and in the same order
  162. :param user_order: order from the user
  163. :param signal_order: order in the signal
  164. :param expected_order_type: type of order (normal, strict, ordered-strict)
  165. :return: True if the order match
  166. """
  167. matching_type_function = {
  168. "normal": cls.is_normal_matching,
  169. "strict": cls.is_strict_matching,
  170. "ordered-strict": cls.is_ordered_strict_matching
  171. }
  172. # Lowercase all incoming
  173. user_order = user_order.lower()
  174. signal_order = signal_order.lower()
  175. if expected_order_type in matching_type_function:
  176. return matching_type_function[expected_order_type](user_order, signal_order)
  177. else:
  178. logger.debug("[OrderAnalyser] non existing matching-type: '%s', fallback to 'normal'" % expected_order_type)
  179. return matching_type_function["normal"](user_order, signal_order)
  180. @classmethod
  181. def _get_instantiated_order_signal_from_user_order(cls, signal_order, user_order):
  182. """
  183. return instantiated signal order with parameters loaded from the user order
  184. E.g:
  185. signal_order = "this is an {{ variable }}
  186. user_order = "this is an order"
  187. returned value is: "this is an order"
  188. :param user_order: the order from the user
  189. :param signal_order: the order with brackets from the synapse
  190. :return: jinja instantiated order from the signal
  191. """
  192. # get parameters
  193. parameters_from_user_order = NeuronParameterLoader.get_parameters(synapse_order=signal_order,
  194. user_order=user_order)
  195. # we load variables into the expected order from the signal
  196. t = Template(signal_order)
  197. signal_order = t.render(**parameters_from_user_order)
  198. return signal_order