FindSimilarIncidentsByText Deprecated
Deprecated. Use DBotFindSimilarIncidents instead. Find similar incidents by text comparison - the algorithm based on TF-IDF method. To read more about this method: https://en.wikipedia.org/wiki/Tf%E2%80%93idf This automation runs using the default Limited User role, unless you explicitly change the permissions. For more information, see the section about permissions here: - For Cortex XSOAR 6 see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.x/Cortex-XSOAR-Playbook-Design-Guide/Automations - For Cortex XSOAR 8 Cloud see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8/Cortex-XSOAR-Cloud-Documentation/Create-a-script - For Cortex XSOAR 8.7 On-prem see https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8.7/Cortex-XSOAR-On-prem-Documentation/Create-a-script
python · Base
Source
import dateutil.parser from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import linear_kernel from six import string_types from CommonServerPython import * INCIDENT_TEXT_FIELD = 'incident_text_for_tfidf' def parse_datetime(datetime_str): return dateutil.parser.parse(datetime_str) def get_similar_texts(text, other_texts): vect = TfidfVectorizer(min_df=1, stop_words='english') if type(text) is not list: text = [text] tfidf = vect.fit_transform(text + list(other_texts)) similarity_vector = linear_kernel(tfidf[0:1], tfidf).flatten() return similarity_vector[1:] def get_texts_from_incident(incident, text_fields): texts = [] # labels for label in incident.get('labels') or []: if label['type'].lower() in text_fields: texts.append(label['value']) # custom fields + incident fields. custom_fields = incident.get('CustomFields') or {} for field_name, field_value in (list(custom_fields.items()) + list(incident.items())): if field_name in text_fields and isinstance(field_value, string_types): texts.append(field_value) return " ".join(texts) def add_text_to_incident(incident, text_fields): incident[INCIDENT_TEXT_FIELD] = get_texts_from_incident(incident, text_fields) def get_incidents_by_time(incident_time, incident_type, incident_id, hours_time_frame, ignore_closed, max_number_of_results, time_field): incident_time = parse_datetime(incident_time) max_date = incident_time + timedelta(hours=hours_time_frame) min_date = incident_time - timedelta(hours=hours_time_frame) query = '{0}:>="{1}" and {0}:<="{2}" and type:"{3}"'.format(time_field, min_date.isoformat(), max_date.isoformat(), incident_type) if ignore_closed: query += " and -status: closed" if incident_id: query += ' and -id:%s' % incident_id args = {'query': query, 'size': max_number_of_results, 'sort': '%s.desc' % time_field} if time_field == "created": args['from'] = min_date.isoformat() res = demisto.executeCommand('GetIncidentsByQuery', { 'query': query, 'fromDate': min_date.isoformat(), 'toDate': max_date.isoformat(), 'limit': max_number_of_results }) if is_error(res): return_error(res) incident_list = json.loads(res[0]['Contents']) if len(res) > 0 else [] return incident_list def incident_to_record(incident, time_field): def parse_time(date_time_str): try: if date_time_str.find('.') > 0: date_time_str = date_time_str[:date_time_str.find('.')] if date_time_str.find('+') > 0: date_time_str = date_time_str[:date_time_str.find('+')] return date_time_str.replace('T', ' ') except Exception: return date_time_str occured_time = parse_time(incident[time_field]) return {'id': "[%s](#/Details/%s)" % (incident['id'], incident['id']), 'rawId': incident['id'], 'name': incident['name'], 'closedTime': parse_time(incident['closed']) if incident['closed'] != "0001-01-01T00:00:00Z" else "", 'Time': occured_time, 'similarity': "{0:.2f}".format(incident['similarity']) } def pre_process_nlp(text_data): res = demisto.executeCommand('WordTokenizerNLP', { 'value': json.dumps(text_data), 'isValueJson': 'yes', }) if is_error(res): return_error(get_error(res)) processed_text_data = res[0]['Contents'] if not isinstance(processed_text_data, list): processed_text_data = [processed_text_data] tokenized_text_data = map(lambda x: x.get('tokenizedText'), processed_text_data) return tokenized_text_data def main(): HOURS_TIME_FRAME = float(demisto.args()['timeFrameHours']) THRESHOLD = float(demisto.args()['threshold']) TEXT_FIELDS = set(map(lambda x: x.lower(), demisto.args()['textFields'].split(','))) IGNORE_CLOSED = demisto.args()['ignoreClosedIncidents'] == 'yes' INCIDENT_QUERY_SIZE = int(demisto.args()['maximumNumberOfIncidents']) MIN_TEXT_LENGTH = int(demisto.args()['minTextLength']) MAX_CANDIDATES_IN_LIST = int(demisto.args()['maxResults']) TIME_FIELD = demisto.args()['timeField'] PRE_PROCESS_TEXT = demisto.args()['preProcessText'] == 'true' incident = demisto.incidents()[0] incident_text = get_texts_from_incident(incident, TEXT_FIELDS) if len(incident_text) < MIN_TEXT_LENGTH: demisto.results("The text is too short to compare - minimum of %d chars required" % MIN_TEXT_LENGTH) sys.exit(0) # get initial candidates list candidates = get_incidents_by_time(incident[TIME_FIELD], incident['type'], incident['id'], HOURS_TIME_FRAME, IGNORE_CLOSED, INCIDENT_QUERY_SIZE, TIME_FIELD) # filter candidates with minimum length constraint for candidate in candidates: add_text_to_incident(candidate, TEXT_FIELDS) # map(lambda x: add_text_to_incident(x, TEXT_FIELDS), candidates) candidates = [x for x in candidates if len(x.get(INCIDENT_TEXT_FIELD, "")) >= MIN_TEXT_LENGTH] # compare candidates to the orginial incident using TF-IDF candidates_text = map(lambda x: x[INCIDENT_TEXT_FIELD], candidates) if PRE_PROCESS_TEXT: incident_text = pre_process_nlp(incident_text) candidates_text = pre_process_nlp(candidates_text) similarity_vector = get_similar_texts(incident_text, candidates_text) similar_incidents = [] for (i, similarity) in enumerate(similarity_vector): candidates[i]['similarity'] = similarity if similarity >= THRESHOLD: similar_incidents.append(candidates[i]) # update context if len(similar_incidents or []) > 0: similar_incidents_rows = map(lambda incident: incident_to_record(incident, TIME_FIELD), similar_incidents) similar_incidents_rows = sorted(similar_incidents_rows, key=lambda x: x['Time']) context = { 'similarIncidentList': similar_incidents_rows[:MAX_CANDIDATES_IN_LIST], 'similarIncident': similar_incidents_rows[0], 'isSimilarIncidentFound': True } markdown_result = tableToMarkdown("Similar incidents", similar_incidents_rows, headers=['id', 'name', 'closedTime', 'Time', 'similarity']) return {'ContentsFormat': formats['markdown'], 'Type': entryTypes['note'], 'Contents': markdown_result, 'EntryContext': context} else: context = { 'isSimilarIncidentFound': False } return {'ContentsFormat': formats['markdown'], 'Type': entryTypes['note'], 'Contents': 'No similar incidents has been found', 'EntryContext': context} if __name__ in ['__main__', '__builtin__', 'builtins']: entry = main() if entry: demisto.results(entry)
README
Find similar incidents by text comparison - the algorithm based on TF-IDF method.
To read more about this method: https://en.wikipedia.org/wiki/Tf%E2%80%93idf
This automation runs using the default Limited User role, unless you explicitly
change the permissions.
For more information, see the section about permissions here:
For Cortex XSOAR 6, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.x/Cortex-XSOAR-Playbook-Design-Guide/Automations for Cortex XSOAR 8 Cloud, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8/Cortex-XSOAR-Cloud-Documentation/Create-a-script for Cortex XSOAR 8 On-prem, see the https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/8.7/Cortex-XSOAR-On-prem-Documentation/Create-a-script.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | ml, dedup, duplicate, incidents |
| Cortex XSOAR Version | 5.0.0 |
Used In
This script is used in the following playbooks and scripts.
- Dedup - Generic
- Dedup - Generic v2
- Dedup - Generic v3
Inputs
| Argument Name | Description |
|---|---|
| textFields | Text fields to compare. Can be label name, incident fields or custom fields. Comma separated value. |
| threshold | TFIDF score threshold (to consider incident as similar). |
| maximumNumberOfIncidents | Maximum number of incidents to check. |
| timeFrameHours | Check incidents in this time frame. |
| ignoreClosedIncidents | Ignore close incidents. |
| timeField | Time field to consider. |
| maxResults | Maximum number of similar candidates. |
| minTextLength | Minimum required text length to compare. |
| preProcessText | Whether to pre-process text (removing HTML, normilize words) |
Outputs
| Path | Description | Type |
|---|---|---|
| similarIncident.rawId | Similar incident ID. | string |
| isSimilarIncidentFound | Is similar incident found? (true\false) | boolean |
| similarIncident | Similar incident. | Unknown |
| similarIncident.name | Similar incident name. | string |