import * as _ from 'lodash'; import * as natural from 'natural'; const NGrams = natural.NGrams; const tokenizer = new natural.WordTokenizer(); import * as util from 'util'; import * as Promise from 'bluebird'; import * as fs from 'fs'; import * as path from 'path'; import { Intent } from '../types/bot'; import { classifier, GenerateClassifier, TopicCollection, Classifiers, Classification, checkUsingClassifier, runThroughClassifiers } from './classifier'; export interface Topics { people: Array; places: Array; } const intentLocation: string = process.env.INTENT_LOCATION ? process.env.INTENT_LOCATION : path.dirname(require.resolve('@alana/intents')); const locations: locationMap = loadLocations(`${intentLocation}/locations`); const dateClassifiers: Classifiers = GenerateClassifier([`${intentLocation}/dates`]); export function grabTopics(text: string): Promise { const datesCompacted = runThroughClassifiers(text, dateClassifiers); const datesGrouped = _.groupBy(datesCompacted, 'topic'); const specials = _.compact(tokenizer.tokenize(text).filter(token => !isNaN(parseInt(token, 10)))); const intent: Intent = { action: null, details: { dates: _.mapValues(datesGrouped, (classifications: Array) => classifications.map(classification => _.startCase(classification.label))), specialWords: specials, locations: locatonExtractor(text), }, domain: 'details', }; // if (this && this.debugOn) { console.log('details intent', util.inspect(intent, { depth: null })); }; return Promise.resolve(intent); } function onlyDirectories(name: string): boolean { return !(_.startsWith(name, '.') || _.endsWith(name, '.json')); } function loadLocations(theDirectory: string): locationMap { const readLocations: locationMap = {}; fs.readdirSync(theDirectory) .filter((aDirectory: string) => onlyDirectories(aDirectory)) .forEach(topic => { readLocations[topic] = {}; fs.readdirSync(`${theDirectory}/${topic}`) .filter(file => !_.startsWith(file, '.')) .forEach(file => { const key: string = /(.*).json/.exec(file)[1].replace(/-/g, ' '); const contents: string = fs.readFileSync(`${theDirectory}/${topic}/${file}`, 'utf8'); const phrases: Array = JSON.parse(contents); readLocations[topic][key] = phrases; }); }); // console.log(readLocations); return readLocations; } export type locationMap = { [s: string]: { [s: string]: Array; }; }; function createTokens(text: string): Array { const tokenized: Array = tokenizer.tokenize(text); const biGrams = NGrams.ngrams(tokenized, 2).map((words) => words.join(' ')); const triGrams = NGrams.ngrams(tokenized, 3).map((words) => words.join(' ')) as Array; const ngrams: Array = _.flatten(tokenized.concat(biGrams, triGrams)) as Array; const allPhrases = ngrams.map(phrase => phrase.toLowerCase()); return allPhrases; } export function locatonExtractor(text: string): any { const allPhrases = createTokens(text); // console.log('allPhrases', allPhrases); const matchingCities = _.mapValues(locations, topic => _.pickBy(topic, (value: Array, key: string) => { const theseCities = value.map(location => location.toLowerCase()); // console.log(theseCities); return _.intersection(allPhrases, theseCities).length > 0; })); // console.log('matchingCities', matchingCities); const pickedTopics = _.pickBy(matchingCities, topic => _.keys(topic).length > 0); const mapped = _.mapValues(pickedTopics, topic => _.keys(topic)); // console.log(matchingCities); return mapped as locationMap; } export function getLocationConfidence(text: string, searchLocation: string): number { let matchingCities = _.map(locations, (subLocations, key) => { // console.log(subLocations); return _.map(subLocations, (cities) => { // console.log(cities, searchLocation); const normalizedCities: Array = cities.map(city => city.toLowerCase()); return _.includes(normalizedCities, searchLocation.toLowerCase()) ? normalizedCities : null; }); }); const cityList = _.compact(_.flattenDeep(matchingCities) as Array); const allPhrases = createTokens(text); const matchingPhrase = _.intersection(allPhrases, cityList); const textTokenized: Array = tokenizer.tokenize(text); const locationTokenized = tokenizer.tokenize(matchingPhrase[0]); return locationTokenized.length / textTokenized.length; }