OrderAnalyser.py 12 KB

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