diff --git a/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Event%20Module/set_model.xml b/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Event%20Module/set_model.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f0d890f6e96162c57cdfb8d40eac13972d956f1e
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Event%20Module/set_model.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+ -
+ action
+
+ AAAAAAAAAAI=
+
+
+ -
+ categories
+
+
+ action_type/object_action
+
+
+
+ -
+ category
+ object_action
+
+ -
+ condition
+
+
+ -
+ description
+
+
+
+
+ -
+ icon
+
+
+ -
+ id
+ set_model
+
+ -
+ permissions
+
+
+ View
+
+
+
+ -
+ portal_type
+ Action Information
+
+ -
+ priority
+ 1.0
+
+ -
+ title
+ Set Web Message Model
+
+ -
+ visible
+ 1
+
+
+
+
+
+
+
+
+
+
+ -
+ text
+ string:${object_url}/WebMessage_setModel
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Event%20Module/test_model.xml b/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Event%20Module/test_model.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fba380112ed0ab8ed9e070ce93caf40080624cd2
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Event%20Module/test_model.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+ -
+ action
+
+ AAAAAAAAAAI=
+
+
+ -
+ categories
+
+
+ action_type/object_action
+
+
+
+ -
+ category
+ object_action
+
+ -
+ condition
+
+
+ -
+ description
+
+
+
+
+ -
+ icon
+
+
+ -
+ id
+ test_model
+
+ -
+ permissions
+
+
+ View
+
+
+
+ -
+ portal_type
+ Action Information
+
+ -
+ priority
+ 1.0
+
+ -
+ title
+ Test Web Message Model
+
+ -
+ visible
+ 1
+
+
+
+
+
+
+
+
+
+
+ -
+ text
+ string:${object_url}/WebMessage_testModel
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Web%20Message/follow_up_automatically.xml b/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Web%20Message/follow_up_automatically.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0ac14f88211471d5f66b7c4dad8c5f4e0454be42
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ActionTemplateItem/portal_types/Web%20Message/follow_up_automatically.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+ -
+ action
+
+ AAAAAAAAAAI=
+
+
+ -
+ categories
+
+
+ action_type/object_action
+
+
+
+ -
+ category
+ object_action
+
+ -
+ condition
+
+
+ -
+ description
+
+
+
+
+ -
+ icon
+
+
+ -
+ id
+ follow_up_automatically
+
+ -
+ permissions
+
+
+ Add portal content
+
+
+
+ -
+ portal_type
+ Action Information
+
+ -
+ priority
+ 1.5
+
+ -
+ title
+ Follow Up Automatically
+
+ -
+ visible
+ 1
+
+
+
+
+
+
+
+
+
+
+ -
+ text
+ string:${object_url}/WebMessage_parseWebMessage
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.ParseWebMessage.py b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.ParseWebMessage.py
new file mode 100644
index 0000000000000000000000000000000000000000..93a7786965874819623fe4f1bdb701b0de0cd0ae
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.ParseWebMessage.py
@@ -0,0 +1,92 @@
+def WebMessage_parseWebMessage(self):
+ '''
+ This function automatically determines the keywords/subject/tags of a message
+ in order to follow up on it
+ '''
+ import pickle
+ message = self.getObject()
+ portal=self.getPortalObject()
+ people = portal.person_module
+ text_content = message.getTextContent()
+ if text_content is None:
+ return()
+ suggested_subject_list = []
+ sender_info=False
+
+ # process header and create person for response
+ line_array = [line for line in text_content.splitlines() if line.strip() != '']
+ if line_array[0][:14] == "
Name":
+ sender_info=True
+ line_array[:4] = [line.split(':')[1][:-4] for line in line_array[:4]]
+ name = line_array[0]
+ email = line_array[2][1:]
+ line_array = line_array[4:]
+ line_array[0] = line_array[0].split(':')[1]
+
+ new_id = str(people.generateNewId())
+ self.portal_types.constructContent(
+ type_name="Person",
+ container=people,
+ id=new_id
+ )
+ person = people[new_id]
+ (first_name, last_name) = (name.split()[0], name.split()[1])
+ person.edit(first_name=first_name, last_name=last_name)
+ person.setEmailText(email)
+ message.setSource("person_module/" + new_id)
+
+ text = ' '.join(line_array)
+
+ # get model from file
+ kw = dict(portal_type = 'File', \
+ reference='ai_business_bot',
+ title="AI Business Bot")
+ erp5_file = portal.portal_catalog.getResultValue(**kw)
+ if not erp5_file:
+ return "No model found to be applied to this Web Message. Run Set Web Message Model in Event Module first."
+ model_as_string = erp5_file.getData()
+ model = pickle.loads(model_as_string)
+ language_arrays = model[0]
+ tag_arrays = model[1]
+ stopwords_arrays = model[2]
+
+ # determine language of message
+ message_language = "en"
+ languages = language_arrays.keys()
+ language_relevance = {languages[i]:0 for i in range(len(languages))}
+ for word in text_content:
+ for language in languages:
+ if word in language_arrays[language]:
+ word_relevance = (language_arrays[language][word])/(list(language_arrays[language].values())[0])
+ language_relevance[language] = language_relevance[language] + word_relevance
+ message_language = max(language_relevance, key=language_relevance.get)
+ suggested_subject_list.append(message_language)
+
+ # clean up text for analysis
+ import string
+ exclude = set(string.punctuation)
+ text = text_content.lower()
+ text = ''.join(ch for ch in text if ch not in exclude)
+ text = [w for w in text if w not in stopwords_arrays[message_language]]
+
+ # determine relevance of each tag to message
+ tag_array = tag_arrays[message_language]
+ tags = tag_array.keys()
+ tag_relevance = {tags[i]:0 for i in range(len(tags))}
+ for word in text:
+ for t in range(len(tags)):
+ if word in tag_array[tags[t]]:
+ word_relevance = (tag_array[tags[t]][word]/list(tag_array[tags[t]].values())[0])
+ tag_relevance[tags[t]] = tag_relevance[tags[t]] + word_relevance
+
+ # apply tags
+ average_relevance = sum(tag_relevance.values()) / float(len(tag_relevance.values()))
+ for t in tag_relevance:
+ if tag_relevance[t] >= average_relevance*2:
+ suggested_subject_list.append(t)
+
+ message.setSubjectList(suggested_subject_list)
+ if sender_info:
+ return self.WebMessage_followUpWebMessage(tags=suggested_subject_list)
+ else:
+ return self.Base_redirect()
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.ParseWebMessage.xml b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.ParseWebMessage.xml
new file mode 100644
index 0000000000000000000000000000000000000000..937fa240eb906893a981686d054cf92efb28d01f
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.ParseWebMessage.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+ -
+ _recorded_property_dict
+
+ AAAAAAAAAAI=
+
+
+ -
+ default_reference
+ Parse Web Message
+
+ -
+ description
+
+
+
+
+ -
+ id
+ extension.erp5.ParseWebMessage
+
+ -
+ portal_type
+ Extension Component
+
+ -
+ sid
+
+
+
+
+ -
+ text_content_error_message
+
+
+
+
+ -
+ text_content_warning_message
+
+
+
+
+ -
+ version
+ erp5
+
+ -
+ workflow_history
+
+ AAAAAAAAAAM=
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
-
+ component_validation_workflow
+
+ AAAAAAAAAAQ=
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ action
+ validate
+
+ -
+ validation_state
+ validated
+
+
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.SetWebMessageModel.py b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.SetWebMessageModel.py
new file mode 100644
index 0000000000000000000000000000000000000000..97b925f344a5a4310fa3987e59ca4188c14c5a9c
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.SetWebMessageModel.py
@@ -0,0 +1,50 @@
+def WebMessage_setModel(self):
+ """
+ Make dictionaries of word counts and save it in ZODB.
+ For AI Business Bot for web messages.
+ """
+
+ from Products.ZSQLCatalog.SQLCatalog import Query
+ from Products.ZSQLCatalog.SQLCatalog import NegatedQuery
+ import datetime
+ import time
+ import pickle
+
+ # instantiate arrays
+ stopwords_arrays = {}
+ stopwords_arrays["en"] = ['p', 'a', 'about', 'above', 'after', 'again', 'against', 'all', 'am', 'an', 'and', 'any', 'are', "aren't", 'as', 'at', 'be', 'because', 'been', 'before', 'being', 'below', 'between', 'both', 'but', 'by', "can't", 'cannot', 'could', "couldn't", 'did', "didn't", 'do', 'does', "doesn't", 'doing', "don't", 'down', 'during', 'each', 'few', 'for', 'from', 'further', 'had', "hadn't", 'has', "hasn't", 'have', "haven't", 'having', 'he', "he'd", "he'll", "he's", 'her', 'here', "here's", 'hers', 'herself', 'him', 'himself', 'his', 'how', "how's", 'i', "i'd", "i'll", "i'm", "i've", 'if', 'in', 'into', 'is', "isn't", 'it', "it's", 'its', 'itself', "let's", 'me', 'more', 'most', "mustn't", 'my', 'myself', 'no', 'nor', 'not', 'of', 'off', 'on', 'once', 'only', 'or', 'other', 'ought', 'our', 'ours\tourselves', 'out', 'over', 'own', 'same', "shan't", 'she', "she'd", "she'll", "she's", 'should', "shouldn't", 'so', 'some', 'such', 'than', 'that', "that's", 'the', 'their', 'theirs', 'them', 'themselves', 'then', 'there', "there's", 'these', 'they', "they'd", "they'll", "they're", "they've", 'this', 'those', 'through', 'to', 'too', 'under', 'until', 'up', 'very', 'was', "wasn't", 'we', "we'd", "we'll", "we're", "we've", 'were', "weren't", 'what', "what's", 'when', "when's", 'where', "where's", 'which', 'while', 'who', "who's", 'whom', 'why', "why's", 'with', "won't", 'would', "wouldn't", 'you', "you'd", "you'll", "you're", "you've", 'your', 'yours', 'yourself', 'yourselves']
+ stopwords_arrays["fr"] = ['p', 'alors', 'au', 'aucuns', 'aussi', 'autre', 'avant', 'avec', 'avoir', 'bon', 'car', 'ce', 'cela', 'ces', 'ceux', 'chaque', 'ci', 'comme', 'comment', 'dans', 'des', 'du', 'dedans', 'dehors', 'depuis', 'devrait', 'doit', 'donc', 'dos', 'd\xc3\xa9but', 'elle', 'elles', 'en', 'encore', 'essai', 'est', 'et', 'eu', 'fait', 'faites', 'fois', 'font', 'hors', 'ici', 'il', 'ils', 'je', 'juste', 'la', 'le', 'les', 'leur', 'l\xc3\xa0', 'ma', 'maintenant', 'mais', 'mes', 'mine', 'moins', 'mon', 'mot', 'm\xc3\xaame', 'ni', 'nomm\xc3\xa9s', 'notre', 'nous', 'ou', 'o\xc3\xb9', 'par', 'parce', 'pas', 'peut', 'peu', 'plupart', 'pour', 'pourquoi', 'quand', 'que', 'quel', 'quelle', 'quelles', 'quels', 'qui', 'sa', 'sans', 'ses', 'seulement', 'si', 'sien', 'son', 'sont', 'sous', 'soyez', 'sujet', 'sur', 'ta', 'tandis', 'tellement', 'tels', 'tes', 'ton', 'tous', 'tout', 'trop', 'tr\xc3\xa8s', 'tu', 'voient', 'vont', 'votre', 'vous', 'vu', '\xc3\xa7a', '\xc3\xa9taient', '\xc3\xa9tat', '\xc3\xa9tions', '\xc3\xa9t\xc3\xa9', '\xc3\xaatre']
+ stopwords_arrays["pt"] = ['p', 'a', 'ainda', 'alem', 'ambas', 'ambos', 'antes', 'ao', 'aonde', 'aos', 'apos', 'aquele', 'aqueles', 'as', 'assim', 'com', 'como', 'contra', 'contudo', 'cuja', 'cujas', 'cujo', 'cujos', 'da', 'das', 'de', 'dela', 'dele', 'deles', 'demais', 'depois', 'desde', 'desta', 'deste', 'dispoe', 'dispoem', 'diversa', 'diversas', 'diversos', 'do', 'dos', 'durante', 'e', 'ela', 'elas', 'ele', 'eles', 'em', 'entao', 'entre', 'essa', 'essas', 'esse', 'esses', 'esta', 'estas', 'este', 'estes', 'ha', 'isso', 'isto', 'logo', 'mais', 'mas', 'mediante', 'menos', 'mesma', 'mesmas', 'mesmo', 'mesmos', 'na', 'nas', 'nao', 'nas', 'nem', 'nesse', 'neste', 'nos', 'o', 'os', 'ou', 'outra', 'outras', 'outro', 'outros', 'pelas', 'pelas', 'pelo', 'pelos', 'perante', 'pois', 'por', 'porque', 'portanto', 'proprio', 'propios', 'quais', 'qual', 'qualquer', 'quando', 'quanto', 'que', 'quem', 'quer', 'se', 'seja', 'sem', 'sendo', 'seu', 'seus', 'sob', 'sobre', 'sua', 'suas', 'tal', 'tambem', 'teu', 'teus', 'toda', 'todas', 'todo', 'todos', 'tua', 'tuas', 'tudo', 'um', 'uma', 'umas', 'uns']
+ stopwords_arrays["ja"] = ['p', '\xe3\x81\x93\xe3\x82\x8c', '\xe3\x81\x9d\xe3\x82\x8c', '\xe3\x81\x82\xe3\x82\x8c', '\xe3\x81\x93\xe3\x81\xae', '\xe3\x81\x9d\xe3\x81\xae', '\xe3\x81\x82\xe3\x81\xae', '\xe3\x81\x93\xe3\x81\x93', '\xe3\x81\x9d\xe3\x81\x93', '\xe3\x81\x82\xe3\x81\x9d\xe3\x81\x93', '\xe3\x81\x93\xe3\x81\xa1\xe3\x82\x89', '\xe3\x81\xa9\xe3\x81\x93', '\xe3\x81\xa0\xe3\x82\x8c', '\xe3\x81\xaa\xe3\x81\xab', '\xe3\x81\xaa\xe3\x82\x93', '\xe4\xbd\x95', '\xe7\xa7\x81', '\xe8\xb2\xb4\xe6\x96\xb9', '\xe8\xb2\xb4\xe6\x96\xb9\xe6\x96\xb9', '\xe6\x88\x91\xe3\x80\x85', '\xe7\xa7\x81\xe9\x81\x94', '\xe3\x81\x82\xe3\x81\xae\xe4\xba\xba', '\xe3\x81\x82\xe3\x81\xae\xe3\x81\x8b\xe3\x81\x9f', '\xe5\xbd\xbc\xe5\xa5\xb3', '\xe5\xbd\xbc', '\xe3\x81\xa7\xe3\x81\x99', '\xe3\x81\x82\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99', '\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99', '\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99', '\xe3\x81\xaf', '\xe3\x81\x8c', '\xe3\x81\xae', '\xe3\x81\xab', '\xe3\x82\x92', '\xe3\x81\xa7', '\xe3\x81\x88', '\xe3\x81\x8b\xe3\x82\x89', '\xe3\x81\xbe\xe3\x81\xa7', '\xe3\x82\x88\xe3\x82\x8a', '\xe3\x82\x82', '\xe3\x81\xa9\xe3\x81\xae', '\xe3\x81\xa8', '\xe3\x81\x97', '\xe3\x81\x9d\xe3\x82\x8c\xe3\x81\xa7', '\xe3\x81\x97\xe3\x81\x8b\xe3\x81\x97']
+ stopwords_arrays["es"] = ['p', 'un', 'una', 'unas', 'unos', 'uno', 'sobre', 'todo', 'tambi\xc3\xa9n', 'tras', 'otro', 'alg\xc3\xban', 'alguno', 'alguna', 'algunos', 'algunas', 'ser', 'es', 'soy', 'eres', 'somos', 'sois', 'estoy', 'esta', 'estamos', 'estais', 'estan', 'como', 'en', 'para', 'atras', 'porque', 'por', 'qu\xc3\xa9', 'estado', 'estaba', 'ante', 'antes', 'siendo', 'ambos', 'pero', 'por', 'poder', 'puede', 'puedo', 'podemos', 'podeis', 'pueden', 'fui', 'fue', 'fuimos', 'fueron', 'hacer', 'hago', 'hace', 'hacemos', 'haceis', 'hacen', 'cada', 'fin', 'incluso', 'primero', 'desde', 'conseguir', 'consigo', 'consigue', 'consigues', 'conseguimos', 'consiguen', 'ir', 'voy', 'va', 'vamos', 'vais', 'van', 'vaya', 'gueno', 'ha', 'tener', 'tengo', 'tiene', 'tenemos', 'teneis', 'tienen', 'el', 'la', 'lo', 'las', 'los', 'su', 'aqui', 'mio', 'tuyo', 'ellos', 'ellas', 'nos', 'nosotros', 'vosotros', 'vosotras', 'si', 'dentro', 'solo', 'solamente', 'saber', 'sabes', 'sabe', 'sabemos', 'sabeis', 'saben', 'ultimo', 'largo', 'bastante', 'haces', 'muchos', 'aquellos', 'aquellas', 'sus', 'entonces', 'tiempo', 'verdad', 'verdadero', 'verdadera', 'cierto', 'ciertos', 'cierta', 'ciertas', 'intentar', 'intento', 'intenta', 'intentas', 'intentamos', 'intentais', 'intentan', 'dos', 'bajo', 'arriba', 'encima', 'usar', 'uso', 'usas', 'usa', 'usamos', 'usais', 'usan', 'emplear', 'empleo', 'empleas', 'emplean', 'ampleamos', 'empleais', 'valor', 'muy', 'era', 'eras', 'eramos', 'eran', 'modo', 'bien', 'cual', 'cuando', 'donde', 'mientras', 'quien', 'con', 'entre', 'sin', 'trabajo', 'trabajar', 'trabajas', 'trabaja', 'trabajamos', 'trabajais', 'trabajan', 'podria', 'podrias', 'podriamos', 'podrian', 'podriais', 'yo', 'aquel']
+ stopwords_arrays["de"] = ['p', 'aber', 'als', 'am', 'an', 'auch', 'auf', 'aus', 'bei', 'bin', 'bis', 'bist', 'da', 'dadurch', 'daher', 'darum', 'das', 'da\xc3\x9f', 'dass', 'dein', 'deine', 'dem', 'den', 'der', 'des', 'dessen', 'deshalb', 'die', 'dies', 'dieser', 'dieses', 'doch', 'dort', 'du', 'durch', 'ein', 'eine', 'einem', 'einen', 'einer', 'eines', 'er', 'es', 'euer', 'eure', 'f\xc3\xbcr', 'hatte', 'hatten', 'hattest', 'hattet', 'hier', 'hinter', 'ich', 'ihr', 'ihre', 'im', 'in', 'ist', 'ja', 'jede', 'jedem', 'jeden', 'jeder', 'jedes', 'jener', 'jenes', 'jetzt', 'kann', 'kannst', 'k\xc3\xb6nnen', 'k\xc3\xb6nnt', 'machen', 'mein', 'meine', 'mit', 'mu\xc3\x9f', 'mu\xc3\x9ft', 'musst', 'm\xc3\xbcssen', 'm\xc3\xbc\xc3\x9ft', 'nach', 'nachdem', 'nein', 'nicht', 'nun', 'oder', 'seid', 'sein', 'seine', 'sich', 'sie', 'sind', 'soll', 'sollen', 'sollst', 'sollt', 'sonst', 'soweit', 'sowie', 'und', 'unser', 'unsere', 'unter', 'vom', 'von', 'vor', 'wann', 'warum', 'was', 'weiter', 'weitere', 'wenn', 'wer', 'werde', 'werden', 'werdet', 'weshalb', 'wie', 'wieder', 'wieso', 'wir', 'wird', 'wirst', 'wo', 'woher', 'wohin', 'zu', 'zum', 'zur', '\xc3\xbcber']
+ language_arrays = {"en":{}, "fr":{}, "pt":{}, "ja":{}, "es":{}, "de":{}}
+ tag_arrays = {i: {} for i in language_arrays.keys()}
+
+ # fit the model
+ start_time = time.time()
+ training_messages = self.portal_catalog.searchResults(
+ portal_type="Web Message",
+ query=NegatedQuery(Query(subject=None)),
+ )
+ if not training_messages:
+ return "No Web Messages to train on"
+ for message in training_messages:
+ (language_arrays, tag_arrays) = message.WebMessage_trainOnWebMessage(language_arrays, tag_arrays, stopwords_arrays)
+ end_time = time.time()
+ uptime = end_time - start_time
+ human_uptime = str(datetime.timedelta(seconds=int(uptime)))
+
+ # save the model in ZODB
+ model = (language_arrays, tag_arrays, stopwords_arrays)
+ model_as_string = pickle.dumps(model)
+ kw = dict(portal_type = 'File', \
+ reference = 'ai_business_bot',
+ title = "AI Business Bot")
+ erp5_file = self.portal_catalog.getResultValue(**kw)
+ if erp5_file is None:
+ # create it
+ erp5_file = self.document_module.newContent(**kw)
+ erp5_file.setData(model_as_string)
+
+ return "Model created at " + erp5_file.getPath() + " in " + human_uptime
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.SetWebMessageModel.xml b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.SetWebMessageModel.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7f9d895208d3d22aa2921b63c918bf94f250f640
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.SetWebMessageModel.xml
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+ -
+ _recorded_property_dict
+
+ AAAAAAAAAAI=
+
+
+ -
+ default_reference
+ Set Web Message Model
+
+ -
+ description
+
+
+
+
+ -
+ id
+ extension.erp5.SetWebMessageModel
+
+ -
+ portal_type
+ Extension Component
+
+ -
+ sid
+
+
+
+
+ -
+ version
+ erp5
+
+ -
+ workflow_history
+
+ AAAAAAAAAAM=
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
-
+ component_validation_workflow
+
+ AAAAAAAAAAQ=
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ action
+ validate
+
+ -
+ error_message
+
+
+ -
+ validation_state
+ validated
+
+
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TestWebMessageModel.py b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TestWebMessageModel.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b857300af3f8ab87d27849fa95dd63fba7260b8
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TestWebMessageModel.py
@@ -0,0 +1,131 @@
+def WebMessage_testModel(self):
+ """
+ Test the accuracy of the web message model
+ """
+
+ from Products.ZSQLCatalog.SQLCatalog import Query
+ from Products.ZSQLCatalog.SQLCatalog import NegatedQuery
+ import datetime
+ import time
+ import random
+
+ # instantiate arrays
+ stopwords_arrays = {}
+ stopwords_arrays["en"] = ['p', 'a', 'about', 'above', 'after', 'again', 'against', 'all', 'am', 'an', 'and', 'any', 'are', "aren't", 'as', 'at', 'be', 'because', 'been', 'before', 'being', 'below', 'between', 'both', 'but', 'by', "can't", 'cannot', 'could', "couldn't", 'did', "didn't", 'do', 'does', "doesn't", 'doing', "don't", 'down', 'during', 'each', 'few', 'for', 'from', 'further', 'had', "hadn't", 'has', "hasn't", 'have', "haven't", 'having', 'he', "he'd", "he'll", "he's", 'her', 'here', "here's", 'hers', 'herself', 'him', 'himself', 'his', 'how', "how's", 'i', "i'd", "i'll", "i'm", "i've", 'if', 'in', 'into', 'is', "isn't", 'it', "it's", 'its', 'itself', "let's", 'me', 'more', 'most', "mustn't", 'my', 'myself', 'no', 'nor', 'not', 'of', 'off', 'on', 'once', 'only', 'or', 'other', 'ought', 'our', 'ours\tourselves', 'out', 'over', 'own', 'same', "shan't", 'she', "she'd", "she'll", "she's", 'should', "shouldn't", 'so', 'some', 'such', 'than', 'that', "that's", 'the', 'their', 'theirs', 'them', 'themselves', 'then', 'there', "there's", 'these', 'they', "they'd", "they'll", "they're", "they've", 'this', 'those', 'through', 'to', 'too', 'under', 'until', 'up', 'very', 'was', "wasn't", 'we', "we'd", "we'll", "we're", "we've", 'were', "weren't", 'what', "what's", 'when', "when's", 'where', "where's", 'which', 'while', 'who', "who's", 'whom', 'why', "why's", 'with', "won't", 'would', "wouldn't", 'you', "you'd", "you'll", "you're", "you've", 'your', 'yours', 'yourself', 'yourselves']
+ stopwords_arrays["fr"] = ['p', 'alors', 'au', 'aucuns', 'aussi', 'autre', 'avant', 'avec', 'avoir', 'bon', 'car', 'ce', 'cela', 'ces', 'ceux', 'chaque', 'ci', 'comme', 'comment', 'dans', 'des', 'du', 'dedans', 'dehors', 'depuis', 'devrait', 'doit', 'donc', 'dos', 'd\xc3\xa9but', 'elle', 'elles', 'en', 'encore', 'essai', 'est', 'et', 'eu', 'fait', 'faites', 'fois', 'font', 'hors', 'ici', 'il', 'ils', 'je', 'juste', 'la', 'le', 'les', 'leur', 'l\xc3\xa0', 'ma', 'maintenant', 'mais', 'mes', 'mine', 'moins', 'mon', 'mot', 'm\xc3\xaame', 'ni', 'nomm\xc3\xa9s', 'notre', 'nous', 'ou', 'o\xc3\xb9', 'par', 'parce', 'pas', 'peut', 'peu', 'plupart', 'pour', 'pourquoi', 'quand', 'que', 'quel', 'quelle', 'quelles', 'quels', 'qui', 'sa', 'sans', 'ses', 'seulement', 'si', 'sien', 'son', 'sont', 'sous', 'soyez', 'sujet', 'sur', 'ta', 'tandis', 'tellement', 'tels', 'tes', 'ton', 'tous', 'tout', 'trop', 'tr\xc3\xa8s', 'tu', 'voient', 'vont', 'votre', 'vous', 'vu', '\xc3\xa7a', '\xc3\xa9taient', '\xc3\xa9tat', '\xc3\xa9tions', '\xc3\xa9t\xc3\xa9', '\xc3\xaatre']
+ stopwords_arrays["pt"] = ['p', 'a', 'ainda', 'alem', 'ambas', 'ambos', 'antes', 'ao', 'aonde', 'aos', 'apos', 'aquele', 'aqueles', 'as', 'assim', 'com', 'como', 'contra', 'contudo', 'cuja', 'cujas', 'cujo', 'cujos', 'da', 'das', 'de', 'dela', 'dele', 'deles', 'demais', 'depois', 'desde', 'desta', 'deste', 'dispoe', 'dispoem', 'diversa', 'diversas', 'diversos', 'do', 'dos', 'durante', 'e', 'ela', 'elas', 'ele', 'eles', 'em', 'entao', 'entre', 'essa', 'essas', 'esse', 'esses', 'esta', 'estas', 'este', 'estes', 'ha', 'isso', 'isto', 'logo', 'mais', 'mas', 'mediante', 'menos', 'mesma', 'mesmas', 'mesmo', 'mesmos', 'na', 'nas', 'nao', 'nas', 'nem', 'nesse', 'neste', 'nos', 'o', 'os', 'ou', 'outra', 'outras', 'outro', 'outros', 'pelas', 'pelas', 'pelo', 'pelos', 'perante', 'pois', 'por', 'porque', 'portanto', 'proprio', 'propios', 'quais', 'qual', 'qualquer', 'quando', 'quanto', 'que', 'quem', 'quer', 'se', 'seja', 'sem', 'sendo', 'seu', 'seus', 'sob', 'sobre', 'sua', 'suas', 'tal', 'tambem', 'teu', 'teus', 'toda', 'todas', 'todo', 'todos', 'tua', 'tuas', 'tudo', 'um', 'uma', 'umas', 'uns']
+ stopwords_arrays["ja"] = ['p', '\xe3\x81\x93\xe3\x82\x8c', '\xe3\x81\x9d\xe3\x82\x8c', '\xe3\x81\x82\xe3\x82\x8c', '\xe3\x81\x93\xe3\x81\xae', '\xe3\x81\x9d\xe3\x81\xae', '\xe3\x81\x82\xe3\x81\xae', '\xe3\x81\x93\xe3\x81\x93', '\xe3\x81\x9d\xe3\x81\x93', '\xe3\x81\x82\xe3\x81\x9d\xe3\x81\x93', '\xe3\x81\x93\xe3\x81\xa1\xe3\x82\x89', '\xe3\x81\xa9\xe3\x81\x93', '\xe3\x81\xa0\xe3\x82\x8c', '\xe3\x81\xaa\xe3\x81\xab', '\xe3\x81\xaa\xe3\x82\x93', '\xe4\xbd\x95', '\xe7\xa7\x81', '\xe8\xb2\xb4\xe6\x96\xb9', '\xe8\xb2\xb4\xe6\x96\xb9\xe6\x96\xb9', '\xe6\x88\x91\xe3\x80\x85', '\xe7\xa7\x81\xe9\x81\x94', '\xe3\x81\x82\xe3\x81\xae\xe4\xba\xba', '\xe3\x81\x82\xe3\x81\xae\xe3\x81\x8b\xe3\x81\x9f', '\xe5\xbd\xbc\xe5\xa5\xb3', '\xe5\xbd\xbc', '\xe3\x81\xa7\xe3\x81\x99', '\xe3\x81\x82\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99', '\xe3\x81\x8a\xe3\x82\x8a\xe3\x81\xbe\xe3\x81\x99', '\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99', '\xe3\x81\xaf', '\xe3\x81\x8c', '\xe3\x81\xae', '\xe3\x81\xab', '\xe3\x82\x92', '\xe3\x81\xa7', '\xe3\x81\x88', '\xe3\x81\x8b\xe3\x82\x89', '\xe3\x81\xbe\xe3\x81\xa7', '\xe3\x82\x88\xe3\x82\x8a', '\xe3\x82\x82', '\xe3\x81\xa9\xe3\x81\xae', '\xe3\x81\xa8', '\xe3\x81\x97', '\xe3\x81\x9d\xe3\x82\x8c\xe3\x81\xa7', '\xe3\x81\x97\xe3\x81\x8b\xe3\x81\x97']
+ stopwords_arrays["es"] = ['p', 'un', 'una', 'unas', 'unos', 'uno', 'sobre', 'todo', 'tambi\xc3\xa9n', 'tras', 'otro', 'alg\xc3\xban', 'alguno', 'alguna', 'algunos', 'algunas', 'ser', 'es', 'soy', 'eres', 'somos', 'sois', 'estoy', 'esta', 'estamos', 'estais', 'estan', 'como', 'en', 'para', 'atras', 'porque', 'por', 'qu\xc3\xa9', 'estado', 'estaba', 'ante', 'antes', 'siendo', 'ambos', 'pero', 'por', 'poder', 'puede', 'puedo', 'podemos', 'podeis', 'pueden', 'fui', 'fue', 'fuimos', 'fueron', 'hacer', 'hago', 'hace', 'hacemos', 'haceis', 'hacen', 'cada', 'fin', 'incluso', 'primero', 'desde', 'conseguir', 'consigo', 'consigue', 'consigues', 'conseguimos', 'consiguen', 'ir', 'voy', 'va', 'vamos', 'vais', 'van', 'vaya', 'gueno', 'ha', 'tener', 'tengo', 'tiene', 'tenemos', 'teneis', 'tienen', 'el', 'la', 'lo', 'las', 'los', 'su', 'aqui', 'mio', 'tuyo', 'ellos', 'ellas', 'nos', 'nosotros', 'vosotros', 'vosotras', 'si', 'dentro', 'solo', 'solamente', 'saber', 'sabes', 'sabe', 'sabemos', 'sabeis', 'saben', 'ultimo', 'largo', 'bastante', 'haces', 'muchos', 'aquellos', 'aquellas', 'sus', 'entonces', 'tiempo', 'verdad', 'verdadero', 'verdadera', 'cierto', 'ciertos', 'cierta', 'ciertas', 'intentar', 'intento', 'intenta', 'intentas', 'intentamos', 'intentais', 'intentan', 'dos', 'bajo', 'arriba', 'encima', 'usar', 'uso', 'usas', 'usa', 'usamos', 'usais', 'usan', 'emplear', 'empleo', 'empleas', 'emplean', 'ampleamos', 'empleais', 'valor', 'muy', 'era', 'eras', 'eramos', 'eran', 'modo', 'bien', 'cual', 'cuando', 'donde', 'mientras', 'quien', 'con', 'entre', 'sin', 'trabajo', 'trabajar', 'trabajas', 'trabaja', 'trabajamos', 'trabajais', 'trabajan', 'podria', 'podrias', 'podriamos', 'podrian', 'podriais', 'yo', 'aquel']
+ stopwords_arrays["de"] = ['p', 'aber', 'als', 'am', 'an', 'auch', 'auf', 'aus', 'bei', 'bin', 'bis', 'bist', 'da', 'dadurch', 'daher', 'darum', 'das', 'da\xc3\x9f', 'dass', 'dein', 'deine', 'dem', 'den', 'der', 'des', 'dessen', 'deshalb', 'die', 'dies', 'dieser', 'dieses', 'doch', 'dort', 'du', 'durch', 'ein', 'eine', 'einem', 'einen', 'einer', 'eines', 'er', 'es', 'euer', 'eure', 'f\xc3\xbcr', 'hatte', 'hatten', 'hattest', 'hattet', 'hier', 'hinter', 'ich', 'ihr', 'ihre', 'im', 'in', 'ist', 'ja', 'jede', 'jedem', 'jeden', 'jeder', 'jedes', 'jener', 'jenes', 'jetzt', 'kann', 'kannst', 'k\xc3\xb6nnen', 'k\xc3\xb6nnt', 'machen', 'mein', 'meine', 'mit', 'mu\xc3\x9f', 'mu\xc3\x9ft', 'musst', 'm\xc3\xbcssen', 'm\xc3\xbc\xc3\x9ft', 'nach', 'nachdem', 'nein', 'nicht', 'nun', 'oder', 'seid', 'sein', 'seine', 'sich', 'sie', 'sind', 'soll', 'sollen', 'sollst', 'sollt', 'sonst', 'soweit', 'sowie', 'und', 'unser', 'unsere', 'unter', 'vom', 'von', 'vor', 'wann', 'warum', 'was', 'weiter', 'weitere', 'wenn', 'wer', 'werde', 'werden', 'werdet', 'weshalb', 'wie', 'wieder', 'wieso', 'wir', 'wird', 'wirst', 'wo', 'woher', 'wohin', 'zu', 'zum', 'zur', '\xc3\xbcber']
+ language_arrays = {"en":{}, "fr":{}, "pt":{}, "ja":{}, "es":{}, "de":{}}
+ tag_arrays = {i: {} for i in language_arrays.keys()}
+
+ # fit the model
+ start_time = time.time()
+ test_messages = []
+ training_messages = self.portal_catalog.searchResults(
+ portal_type="Web Message",
+ query=NegatedQuery(Query(subject=None)),
+ )
+ if not training_messages:
+ return "No Web Messages found to train on"
+ for index, message in enumerate(training_messages):
+ if random.random() <= 0.2:
+ test_messages.append(message)
+ else:
+ (language_arrays, tag_arrays) = message.WebMessage_trainOnWebMessage(language_arrays, tag_arrays, stopwords_arrays)
+
+ so = {"sale", "pricing", "demo", "partnership", "advertising"}
+ sr = {"help", "starting", "install", "bug"}
+ m = {"job", "sponsorship", "academic", "contributor"}
+ correct_tags = 0
+ excess_tags = 0
+ language_accuracy = 0
+ type_accuracy = 0
+
+ for message in test_messages:
+ suggested_subject_list = []
+
+ # clean up header from contact form, if there is one
+ text = message.getTextContent()
+ if text is None:
+ pass
+ line_array = [line for line in text.splitlines() if line.strip() != '']
+ if line_array[0][:6] == " Name":
+ line_array = line_array[4:]
+ line_array[0] = line_array[0][14:]
+ text = ' '.join(line_array)
+
+ # determine language of message
+ message_language = "en"
+ languages = language_arrays.keys()
+ language_relevance = {languages[i]:0 for i in range(len(languages))}
+ for word in text:
+ for language in languages:
+ if word in language_arrays[language]:
+ word_relevance = (language_arrays[language][word])/(list(language_arrays[language].values())[0])
+ language_relevance[language] = language_relevance[language] + word_relevance
+ message_language = max(language_relevance, key=language_relevance.get)
+ if message_language != "en":
+ suggested_subject_list.append(message_language)
+
+ # clean up text for analysis
+ import string
+ exclude = set(string.punctuation)
+ text = text.lower()
+ text = ''.join(ch for ch in text if ch not in exclude)
+ text = [w for w in text if w not in stopwords_arrays[message_language]]
+
+ # determine relevance of each tag to message
+ tag_array = tag_arrays[message_language]
+ tags = tag_array.keys()
+ tag_relevance = {tags[i]:0 for i in range(len(tags))}
+ for word in text:
+ for t in range(len(tags)):
+ if word in tag_array[tags[t]]:
+ word_relevance = (tag_array[tags[t]][word]/list(tag_array[tags[t]].values())[0])
+ tag_relevance[tags[t]] = tag_relevance[tags[t]] + word_relevance
+
+ # apply tags
+ average_relevance = sum(tag_relevance.values()) / (len(tag_relevance.values()))
+ for t in tag_relevance:
+ if tag_relevance[t] >= average_relevance*2:
+ suggested_subject_list.append(t)
+
+ # test applied tags for accuracy
+ message_tags = message.getSubjectList()
+ message_tags_set = set(message_tags)
+ suggested_tags_set = set(suggested_subject_list)
+
+ correct_tags += len(suggested_tags_set.intersection(message_tags_set)) / len(message_tags_set)
+ if len(suggested_tags_set) != 0:
+ excess_tags += len(suggested_tags_set.difference(message_tags_set)) / len(suggested_tags_set)
+
+ correct_language = True
+ for language in languages:
+ if language in message_tags_set.symmetric_difference(suggested_tags_set):
+ correct_language = False
+ if correct_language == True:
+ language_accuracy += 1
+
+ if message_tags_set.intersection(sr):
+ if suggested_tags_set.intersection(sr):
+ type_accuracy += 1
+ elif message_tags_set.intersection(so):
+ if suggested_tags_set.intersection(so):
+ type_accuracy += 1
+ else:
+ if not suggested_tags_set.intersection(sr) and not suggested_tags_set.intersection(so):
+ type_accuracy += 1
+
+ if not len(test_messages) == 0:
+ correct_tags = float(correct_tags) / float(len(test_messages))
+ excess_tags = float(excess_tags) / float(len(test_messages))
+ language_accuracy = float(language_accuracy) / float(len(test_messages))
+ type_accuracy = float(type_accuracy) / float(len(test_messages))
+ end_time = time.time()
+ uptime = end_time - start_time
+ human_uptime = str(datetime.timedelta(seconds=int(uptime)))
+
+ return "Model tested in " + human_uptime + " showed a language accuracy of " + str(language_accuracy) + \
+ ", and a ticket_type accuracy of " + str(type_accuracy) + ", identifying " + str(correct_tags) + " of the tags correctly with " + str(excess_tags) + " excess tags."
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TestWebMessageModel.xml b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TestWebMessageModel.xml
new file mode 100644
index 0000000000000000000000000000000000000000..17fb7c5c80d7ee435adc38af564596bb3f7c89ac
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TestWebMessageModel.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+ -
+ _recorded_property_dict
+
+ AAAAAAAAAAI=
+
+
+ -
+ default_reference
+ Test Web Message Model
+
+ -
+ description
+
+
+
+
+ -
+ id
+ extension.erp5.TestWebMessageModel
+
+ -
+ portal_type
+ Extension Component
+
+ -
+ sid
+
+
+
+
+ -
+ text_content_error_message
+
+
+
+
+ -
+ text_content_warning_message
+
+
+ W: 32, 6: Unused variable \'index\' (unused-variable)
+ W: 40, 2: Unused variable \'m\' (unused-variable)
+
+
+
+ -
+ version
+ erp5
+
+ -
+ workflow_history
+
+ AAAAAAAAAAM=
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
-
+ component_validation_workflow
+
+ AAAAAAAAAAQ=
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ action
+ validate
+
+ -
+ validation_state
+ validated
+
+
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TrainOnWebMessage.py b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TrainOnWebMessage.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cf883c3145ddd65f4e41f53c8e5bf443fbe3291
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TrainOnWebMessage.py
@@ -0,0 +1,46 @@
+def WebMessage_trainOnWebMessage(self, language_arrays, tag_arrays, stopwords_arrays):
+ message_tags = self.getSubjectList()
+ if message_tags == []:
+ return (language_arrays, tag_arrays)
+
+ # clean up header from contact form, if there is one
+ text = self.getTextContent()
+ if text is None:
+ return (language_arrays, tag_arrays)
+ line_array = [line for line in text.splitlines() if line.strip() != '']
+ if line_array[0][:6] == " Name":
+ line_array = line_array[4:]
+ line_array[0] = line_array[0][14:]
+ text = ' '.join(line_array)
+
+ # determine message language
+ message_language = "en"
+ languages = language_arrays.keys()
+ for language in languages:
+ if language in message_tags:
+ message_language = language
+ message_tags.remove(language)
+
+ # clean up text for training
+ import string
+ exclude = set(string.punctuation)
+ text = text.lower()
+ text = ''.join(ch for ch in text if ch not in exclude)
+ text = [w for w in text if w not in stopwords_arrays[message_language]]
+
+ # add text into language_arrays and tag_arrays
+ for word in text:
+ language_arrays[message_language][word] = language_arrays[message_language].get(word, 1) + 1
+
+ tag_array = tag_arrays[message_language]
+ tags = tag_array.keys()
+ for word in text:
+ for t in range(len(message_tags)):
+ if message_tags[t] in tags:
+ tag_array[message_tags[t]][word] = tag_array[message_tags[t]].get(word, 1) + 1
+ else:
+ tag_array[message_tags[t]] = {}
+ tag_array[message_tags[t]][word] = 1
+
+ tag_arrays[message_language] = tag_array
+ return (language_arrays, tag_arrays)
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TrainOnWebMessage.xml b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TrainOnWebMessage.xml
new file mode 100644
index 0000000000000000000000000000000000000000..02be514dfce845de44318846c03353452260ea5e
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/ExtensionTemplateItem/portal_components/extension.erp5.TrainOnWebMessage.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+ -
+ _recorded_property_dict
+
+ AAAAAAAAAAI=
+
+
+ -
+ default_reference
+ Train On Web Message
+
+ -
+ description
+
+
+
+
+ -
+ id
+ extension.erp5.TrainOnWebMessage
+
+ -
+ portal_type
+ Extension Component
+
+ -
+ sid
+
+
+
+
+ -
+ text_content_error_message
+
+
+
+
+ -
+ text_content_warning_message
+
+
+
+
+ -
+ version
+ erp5
+
+ -
+ workflow_history
+
+ AAAAAAAAAAM=
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
-
+ component_validation_workflow
+
+ AAAAAAAAAAQ=
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ action
+ validate
+
+ -
+ validation_state
+ validated
+
+
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot.xml b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c2df62ddddb27219097cb6995d370d2d72996b20
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+ -
+ _objects
+
+
+
+
+ -
+ id
+ erp5_business_bot
+
+ -
+ title
+ Business Bot
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_followUpWebMessage.py b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_followUpWebMessage.py
new file mode 100644
index 0000000000000000000000000000000000000000..4cadd382c85ea39c2246b5404bd7d2cab3a5bdb4
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_followUpWebMessage.py
@@ -0,0 +1,69 @@
+# This script creates a new ticket object from this current event based on its tags
+translateString = context.Base_translateString
+portal = context.getPortalObject()
+current_object = context.getObject()
+
+if tags == None:
+ return()
+assign = None #
+
+# Find appropriate ticket type
+tagset = set(tags)
+so = {"sale", "pricing", "demo", "partnership", "advertising"}
+sr = {"help", "starting", "install", "bug"}
+m = {"job", "sponsorship", "academic", "contributor"}
+
+if tagset.intersection(sr):
+ module = context.getPortalObject().support_request_module
+ ticket_type = "Support Request"
+elif tagset.intersection(so):
+ module = context.getPortalObject().sale_opportunity_module
+ ticket_type = "Sale Opportunity"
+else:
+ module = context.getPortalObject().meeting_module
+ ticket_type = "Meeting"
+
+if not portal.Base_checkPermission(module.getId(), "Add portal content"):
+ return context.Base_redirect(
+ form_id,
+ keep_items=dict(
+ portal_status_message=translateString(
+ "You do not have permission to add new ticket.")
+ )
+ )
+
+# Create a new object
+new_id = str(module.generateNewId())
+context.portal_types.constructContent(
+ type_name=ticket_type,
+ container=module,
+ id=new_id
+)
+new_object = module[new_id]
+
+# If we do this before, each added line will take 20 times more time
+# because of programmable acquisition
+new_object.edit(
+ title=current_object.getTitle(),
+ destination_decision_list=current_object.getSourceList(),
+ source_decision_list=current_object.getDestinationList(),
+ start_date=current_object.getStartDate()
+)
+# Now create the relation between the current object and the new one
+current_object.setFollowUpValueList([new_object])
+if assign:
+ new_object.setSourceTrade([assign[1]])
+
+# Redirect to new object
+if assign == None:
+ message = translateString(
+ "Created and associated a new ${ticket_type} for ${title}. Here is a recommended response.",
+ mapping=dict(ticket_type = translateString(ticket_type), title = current_object.getTitle()))
+ return current_object.Base_redirect('WebMessage_viewCreateResponseDialog', keep_items={'portal_status_message': message})
+
+else:
+ name = assign[0]
+ message = translateString(
+ "Created and associated a new ${ticket_type} for ${title}. " + name + " is recommended to handle it",
+ mapping=dict(ticket_type = translateString(ticket_type), title = current_object.getTitle()))
+ return new_object.Base_redirect('view', keep_items={'portal_status_message': message})
diff --git a/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_followUpWebMessage.xml b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_followUpWebMessage.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ef88d8b0b995f3b452db134534005367f62459bb
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_followUpWebMessage.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+ -
+ Script_magic
+ 3
+
+ -
+ _bind_names
+
+
+
+
+ -
+ _params
+ tags=None
+
+ -
+ id
+ WebMessage_followUpWebMessage
+
+ -
+ title
+ Follow Up Web Message
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_parseWebMessage.xml b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_parseWebMessage.xml
new file mode 100644
index 0000000000000000000000000000000000000000..87a71380ae5ee1b3b865de8c3a46844d84c3466c
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_parseWebMessage.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+ -
+ _function
+ WebMessage_parseWebMessage
+
+ -
+ _module
+ Parse Web Message
+
+ -
+ id
+ WebMessage_parseWebMessage
+
+ -
+ title
+ Parse Web Message
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_setModel.xml b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_setModel.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cca59ca74ca47c7f716f44821d79a3daecea9bb1
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_setModel.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+ -
+ _function
+ WebMessage_setModel
+
+ -
+ _module
+ Set Web Message Model
+
+ -
+ id
+ WebMessage_setModel
+
+ -
+ title
+ Set Web Message Model
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_testModel.xml b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_testModel.xml
new file mode 100644
index 0000000000000000000000000000000000000000..10901ed32899de761cd44c8b55d5a0016eee80e0
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_testModel.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+ -
+ _function
+ WebMessage_testModel
+
+ -
+ _module
+ Test Web Message Model
+
+ -
+ id
+ WebMessage_testModel
+
+ -
+ title
+ Test Web Message Model
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_trainOnWebMessage.xml b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_trainOnWebMessage.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f3b144f4505d1833eaebc0130f93fddee32405f1
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/SkinTemplateItem/portal_skins/erp5_business_bot/WebMessage_trainOnWebMessage.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+ -
+ _function
+ WebMessage_trainOnWebMessage
+
+ -
+ _module
+ Train On Web Message
+
+ -
+ id
+ WebMessage_trainOnWebMessage
+
+ -
+ title
+ Train On Web Message
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/TestTemplateItem/portal_components/test.erp5.testBusinessBot.py b/bt5/erp5_ai_business_bot/TestTemplateItem/portal_components/test.erp5.testBusinessBot.py
new file mode 100644
index 0000000000000000000000000000000000000000..1926e8af1658b0343faf6dbf0cbca39c42cfd989
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/TestTemplateItem/portal_components/test.erp5.testBusinessBot.py
@@ -0,0 +1,65 @@
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+
+class Test(ERP5TypeTestCase):
+ """
+ A test class to test the Business Bot module
+ """
+
+ def getTitle(self):
+ return "TestBusinessBot"
+
+ def getBusinessTemplateList(self):
+ """
+ Tuple of Business Templates we need to install
+ """
+ return ('erp5_base', 'erp5_web', 'erp5_ingestion_mysql_innodb_catalog', 'erp5_crm', 'erp5_dms', 'erp5_business_bot')
+
+ def afterSetUp(self):
+ """
+ This is ran before anything, used to set the environment
+ """
+ message_list = [
+ dict(title='Tagged Message', subject_list=['ERP5', 'pricing'], text_content="ERP5 pricing"),
+ dict(title='Untagged Message', subject_list=[], text_content="ERP5 pricing"),
+ ]
+ for index, message in enumerate(message_list):
+ kw = dict(portal_type = 'Web Message', title = message_list[index]["title"], subject = message_list[index]["subject_list"])
+ existing = self.portal.portal_catalog.getResultValue(**kw)
+ if existing is None:
+ self.portal.event_module.newContent(**kw)
+
+ self.commit()
+ self.tic()
+
+ def test_setWebMessageModel(self):
+ """
+ Use case: user has one or more tagged messages
+ and wants to train a model on them. This model now
+ exists in the document module.
+ """
+
+ set_model_result = self.portal.event_module.WebMessage_setModel().split()
+ self.assertEqual(set_model_result[0], "Model")
+ kw = dict(portal_type = 'File', title = "AI Business Bot")
+ document = self.portal.portal_catalog.getResultValue(**kw)
+ self.assertEqual(set_model_result[3], "/erp5/" + document.getRelativeUrl())
+
+ def test_testWebMessageModel(self):
+ """
+ Use case: user wants to know how accurate the model
+ would be given the current algorithm for the model and
+ the current tagged messages. Data is returned to user.
+ """
+ self.assertEqual(self.portal.event_module.WebMessage_testModel().split()[0] , "Model")
+
+ def test_followUpAutomatically(self):
+ """
+ Use case: user has an untagged message that they
+ wish would be handled by the model. This message now
+ has tags.
+ """
+ self.portal.event_module.WebMessage_setModel()
+ kw = dict(portal_type = 'Web Message', title='Untagged Message')
+ message = self.portal.portal_catalog.getResultValue(**kw)
+ message.WebMessage_parseWebMessage()
+ self.assertFalse(message.getSubjectList() == "[]")
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/TestTemplateItem/portal_components/test.erp5.testBusinessBot.xml b/bt5/erp5_ai_business_bot/TestTemplateItem/portal_components/test.erp5.testBusinessBot.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9a6e69a7db295a2246f9a3099c3adf0e803043da
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/TestTemplateItem/portal_components/test.erp5.testBusinessBot.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+ -
+ _recorded_property_dict
+
+ AAAAAAAAAAI=
+
+
+ -
+ default_reference
+ testBusinessBot
+
+ -
+ description
+ Test for Business Bot template
+
+ -
+ id
+ test.erp5.testBusinessBot
+
+ -
+ portal_type
+ Test Component
+
+ -
+ sid
+
+
+
+
+ -
+ text_content_error_message
+
+
+
+
+ -
+ text_content_warning_message
+
+
+ W: 25, 15: Unused variable \'message\' (unused-variable)
+
+
+
+ -
+ version
+ erp5
+
+ -
+ workflow_history
+
+ AAAAAAAAAAM=
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ data
+
+
+
-
+ component_validation_workflow
+
+ AAAAAAAAAAQ=
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+ action
+ validate
+
+ -
+ validation_state
+ validated
+
+
+
+
+
+
+
diff --git a/bt5/erp5_ai_business_bot/bt/dependency_list b/bt5/erp5_ai_business_bot/bt/dependency_list
new file mode 100644
index 0000000000000000000000000000000000000000..d28755dccca540c7b10fc14b3e2258420992fd6f
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/dependency_list
@@ -0,0 +1,5 @@
+erp5_base
+erp5_web
+erp5_dms
+erp5_crm
+erp5_ingestion_mysql_innodb_catalog
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/description b/bt5/erp5_ai_business_bot/bt/description
new file mode 100644
index 0000000000000000000000000000000000000000..5f2ca8cc2bd78eb6506d80c928b7188e82a133c3
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/description
@@ -0,0 +1 @@
+Provides the ability to train a model that can automatically tag and process web messages.
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/maintainer_list b/bt5/erp5_ai_business_bot/bt/maintainer_list
new file mode 100644
index 0000000000000000000000000000000000000000..8bc0281dbdf091c56f007421f8a17709a568fb40
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/maintainer_list
@@ -0,0 +1 @@
+Noah Brackenbury
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/template_action_path_list b/bt5/erp5_ai_business_bot/bt/template_action_path_list
new file mode 100644
index 0000000000000000000000000000000000000000..473c34d967598b49cd4527d2fc457584bef47427
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/template_action_path_list
@@ -0,0 +1,3 @@
+Event Module | set_model
+Event Module | test_model
+Web Message | follow_up_automatically
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/template_extension_id_list b/bt5/erp5_ai_business_bot/bt/template_extension_id_list
new file mode 100644
index 0000000000000000000000000000000000000000..c5cc1652142d29e6dce91d3598ba75b3d438b347
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/template_extension_id_list
@@ -0,0 +1,4 @@
+extension.erp5.ParseWebMessage
+extension.erp5.SetWebMessageModel
+extension.erp5.TestWebMessageModel
+extension.erp5.TrainOnWebMessage
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/template_format_version b/bt5/erp5_ai_business_bot/bt/template_format_version
new file mode 100644
index 0000000000000000000000000000000000000000..56a6051ca2b02b04ef92d5150c9ef600403cb1de
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/template_format_version
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/template_skin_id_list b/bt5/erp5_ai_business_bot/bt/template_skin_id_list
new file mode 100644
index 0000000000000000000000000000000000000000..780ef531652d0b7ea0a1ef0dbac90db37519abd8
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/template_skin_id_list
@@ -0,0 +1 @@
+erp5_business_bot
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/template_test_id_list b/bt5/erp5_ai_business_bot/bt/template_test_id_list
new file mode 100644
index 0000000000000000000000000000000000000000..c5c5426af86c70e07edf3bef910ded70b671f088
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/template_test_id_list
@@ -0,0 +1 @@
+test.erp5.testBusinessBot
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/title b/bt5/erp5_ai_business_bot/bt/title
new file mode 100644
index 0000000000000000000000000000000000000000..32a78b9cab0e3f90a0a9481a60bdce63920353b9
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/title
@@ -0,0 +1 @@
+erp5_ai_business_bot
\ No newline at end of file
diff --git a/bt5/erp5_ai_business_bot/bt/version b/bt5/erp5_ai_business_bot/bt/version
new file mode 100644
index 0000000000000000000000000000000000000000..56a6051ca2b02b04ef92d5150c9ef600403cb1de
--- /dev/null
+++ b/bt5/erp5_ai_business_bot/bt/version
@@ -0,0 +1 @@
+1
\ No newline at end of file