@@ -31,6 +31,18 @@ const featureTypeEnum = {
3131 featureToModel : 'modelName' ,
3232 modelToFeature : 'featureName'
3333} ;
34+ const featureProperties = {
35+ entityFeatureToModel : {
36+ 'simple' : 'Entity Extractor' ,
37+ 'list' : 'Closed List Entity Extractor' ,
38+ 'prebuilt' : 'Prebuilt Entity Extractor' ,
39+ 'composite' : 'Composite Entity Extractor' ,
40+ 'regex' : 'Regex Entity Extractor' ,
41+ 'ml' : 'Entity Extractor'
42+ } ,
43+ intentFeatureToModel : 'Intent Classifier' ,
44+ phraseListFeature : 'phraselist'
45+ }
3446const INTENTTYPE = 'intent' ;
3547const parseFileContentsModule = {
3648 /**
@@ -154,9 +166,73 @@ const parseLuAndQnaWithAntlr = async function (parsedContent, fileContent, log,
154166 }
155167
156168 validateNDepthEntities ( parsedContent . LUISJsonStructure . entities , parsedContent . LUISJsonStructure . flatListOfEntityAndRoles , parsedContent . LUISJsonStructure . intents ) ;
169+
170+ // This nDepth child might have been labelled, if so, remove the duplicate simple entity.
171+ // If utterances have this child, then all parents must be included in the label
172+ updateModelBasedOnNDepthEntities ( parsedContent . LUISJsonStructure . utterances , parsedContent . LUISJsonStructure . entities ) ;
173+
157174 if ( parsedContent . LUISJsonStructure . flatListOfEntityAndRoles ) delete parsedContent . LUISJsonStructure . flatListOfEntityAndRoles
158175
159176}
177+
178+ /**
179+ * Helper to update final LUIS model based on labelled nDepth entities.
180+ * @param {Object [] } utterances
181+ * @param {Object [] } entities
182+ */
183+ const updateModelBasedOnNDepthEntities = function ( utterances , entities ) {
184+ // filter to all utterances that have a labelled entity
185+ let utterancesWithLabels = utterances . filter ( utterance => utterance . entities && utterance . entities . length !== 0 ) ;
186+ utterancesWithLabels . forEach ( utterance => {
187+ utterance . entities . forEach ( entityInUtterance => {
188+ // find this entity's root. There can be multiple and if there are, we need to delete the one that does not have children.
189+ let entityFoundInMaster = [ ] ;
190+ entities . forEach ( ( entity , idx ) => {
191+ if ( entity . name == entityInUtterance . entity ) {
192+ entityFoundInMaster . push ( { id : idx , entityRoot : entity , path : '/' } ) ;
193+ }
194+ let entityPath = findEntityPath ( entity , entityInUtterance . entity , "" ) ;
195+ if ( entityPath !== "" ) {
196+ entityFoundInMaster . push ( { id : idx , entityRoot : entity , path : entityPath } ) ;
197+ }
198+ } ) ;
199+ entityFoundInMaster . forEach ( entityInMaster => {
200+ let splitPath = entityInMaster . path . split ( "/" ) . filter ( item => item . trim ( ) !== "" ) ;
201+ if ( entityFoundInMaster . length > 1 && splitPath . length === 0 && ( ! entityInMaster . entityRoot . children || entityInMaster . entityRoot . children . length === 0 ) ) {
202+ // this child needs to be removed. Note: There can only be at most one more entity due to utterance validation rules.
203+ entities . splice ( entityInMaster . id , 1 ) ;
204+ } else {
205+ splitPath . reverse ( ) . forEach ( parent => {
206+ // Ensure each parent is also labelled in this utterance
207+ let parentLabelled = utterance . entities . find ( entityUtt => entityUtt . entity == parent ) ;
208+ if ( ! parentLabelled ) {
209+ let errorMsg = `[ERROR]: Every child entity labelled in an utterance must have its parent labelled in that utterance. Parent "${ parent } " for child "${ entityInUtterance . entity } " is not labelled in utterance "${ utterance . text } " for intent "${ utterance . intent } ".` ;
210+ throw ( new exception ( retCode . errorCode . INVALID_INPUT , errorMsg ) ) ;
211+ }
212+ } )
213+ }
214+ } )
215+ } )
216+ } )
217+ }
218+ /**
219+ * Helper function to recursively find the path to a child entity
220+ * @param {Object } obj
221+ * @param {String } entityName
222+ * @param {String } path
223+ */
224+ const findEntityPath = function ( obj , entityName , path ) {
225+ path = path || "" ;
226+ var fullpath = "" ;
227+ if ( obj . name === entityName ) {
228+ return path ;
229+ } else if ( obj . children && obj . children . length !== 0 ) {
230+ obj . children . forEach ( child => {
231+ fullpath = findEntityPath ( child , entityName , path + "/" + obj . name + "/" + child . name ) || fullpath ;
232+ } )
233+ }
234+ return fullpath ;
235+ }
160236/**
161237 * Helper function to validate and update nDepth entities
162238 * @param {Object[] } collection
@@ -188,21 +264,21 @@ const validateNDepthEntities = function(collection, entitiesAndRoles, intentsCol
188264 if ( featureExists ) {
189265 // is feature phrase list?
190266 if ( featureExists . type == EntityTypeEnum . PHRASELIST ) {
191- child . features [ idx ] = new helperClass . featureToModel ( feature ) ;
267+ child . features [ idx ] = new helperClass . featureToModel ( feature , featureProperties . phraseListFeature ) ;
192268 featureHandled = true ;
193269 } else if ( featureExists . type == EntityTypeEnum . PATTERNANY ) {
194270 let errorMsg = `[Error] line ${ child . context . line } : Invalid child entity definition found. "${ feature } " is of type "${ EntityTypeEnum . PATTERNANY } " in child entity definition "${ child . context . definition } ". Child cannot include a usesFeature of type "${ EntityTypeEnum . PATTERNANY } ".` ;
195271 throw ( new exception ( retCode . errorCode . INVALID_INPUT , errorMsg ) ) ;
196272 } else {
197- child . features [ idx ] = new helperClass . modelToFeature ( feature ) ;
273+ child . features [ idx ] = new helperClass . modelToFeature ( feature , featureProperties . entityFeatureToModel [ featureExists . type ] ) ;
198274 featureHandled = true ;
199275 }
200276 }
201277 if ( ! featureHandled ) {
202278 // find feature as intent
203279 let intentFeatureExists = intentsCollection . find ( i => i . name == feature ) ;
204280 if ( intentFeatureExists ) {
205- child . features [ idx ] = new helperClass . modelToFeature ( feature ) ;
281+ child . features [ idx ] = new helperClass . modelToFeature ( feature , featureProperties . intentFeatureToModel ) ;
206282 featureHandled = true ;
207283 } else {
208284 let errorMsg = `[Error] line ${ child . context . line } : Invalid child entity definition found. No definition found for "${ feature } " in child entity definition "${ child . context . definition } ". Features must be defined before they can be added to a child.` ;
@@ -219,6 +295,10 @@ const validateNDepthEntities = function(collection, entitiesAndRoles, intentsCol
219295 if ( child . context ) {
220296 delete child . context
221297 }
298+
299+ if ( child . features === "" ) {
300+ delete child . features
301+ }
222302 } )
223303} ;
224304/**
@@ -263,7 +343,7 @@ const validateFeatureAssignment = function(srcItemType, srcItemName, tgtFeatureT
263343 * @param {String } featureType
264344 * @param {Object } line
265345 */
266- const addFeatures = function ( tgtItem , feature , featureType , line ) {
346+ const addFeatures = function ( tgtItem , feature , featureType , line , featureProperties ) {
267347 // target item cannot have the same name as the feature name
268348 if ( tgtItem . name === feature ) {
269349 // Item must be defined before being added as a feature.
@@ -278,17 +358,17 @@ const addFeatures = function(tgtItem, feature, featureType, line) {
278358 switch ( featureType ) {
279359 case featureTypeEnum . featureToModel : {
280360 if ( tgtItem . features ) {
281- if ( ! featureAlreadyDefined ) tgtItem . features . push ( new helperClass . featureToModel ( feature ) ) ;
361+ if ( ! featureAlreadyDefined ) tgtItem . features . push ( new helperClass . featureToModel ( feature , featureProperties ) ) ;
282362 } else {
283- tgtItem . features = new Array ( new helperClass . featureToModel ( feature ) ) ;
363+ tgtItem . features = new Array ( new helperClass . featureToModel ( feature , featureProperties ) ) ;
284364 }
285365 break ;
286366 }
287367 case featureTypeEnum . modelToFeature : {
288368 if ( tgtItem . features ) {
289- if ( ! featureAlreadyDefined ) tgtItem . features . push ( new helperClass . modelToFeature ( feature ) ) ;
369+ if ( ! featureAlreadyDefined ) tgtItem . features . push ( new helperClass . modelToFeature ( feature , featureProperties ) ) ;
290370 } else {
291- tgtItem . features = new Array ( new helperClass . modelToFeature ( feature ) ) ;
371+ tgtItem . features = new Array ( new helperClass . modelToFeature ( feature , featureProperties ) ) ;
292372 }
293373 break ;
294374 }
@@ -327,19 +407,19 @@ const parseFeatureSections = function(parsedContent, featuresToProcess) {
327407 if ( entityExists . type === EntityTypeEnum . PHRASELIST ) {
328408 // de-dupe and add features to intent.
329409 validateFeatureAssignment ( section . Type , section . Name , entityExists . type , feature , section . ParseTree . newEntityLine ( ) ) ;
330- addFeatures ( intentExists , feature , featureTypeEnum . featureToModel , section . ParseTree . newEntityLine ( ) ) ;
410+ addFeatures ( intentExists , feature , featureTypeEnum . featureToModel , section . ParseTree . newEntityLine ( ) , featureProperties . phraseListFeature ) ;
331411 // set enabledForAllModels on this phrase list
332412 let plEnity = parsedContent . LUISJsonStructure . model_features . find ( item => item . name == feature ) ;
333413 plEnity . enabledForAllModels = false ;
334414 } else {
335415 // de-dupe and add model to intent.
336416 validateFeatureAssignment ( section . Type , section . Name , entityExists . type , feature , section . ParseTree . newEntityLine ( ) ) ;
337- addFeatures ( intentExists , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) ) ;
417+ addFeatures ( intentExists , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) , featureProperties . entityFeatureToModel [ entityExists . type ] ) ;
338418 }
339419 } else if ( featureIntentExists ) {
340420 // Add intent as a feature to another intent
341421 validateFeatureAssignment ( section . Type , section . Name , INTENTTYPE , feature , section . ParseTree . newEntityLine ( ) ) ;
342- addFeatures ( intentExists , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) ) ;
422+ addFeatures ( intentExists , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) , featureProperties . intentFeatureToModel ) ;
343423 } else {
344424 // Item must be defined before being added as a feature.
345425 let errorMsg = `Features must be defined before assigned to an intent. No definition found for feature "${ feature } " in usesFeature definition for intent "${ section . Name } "` ;
@@ -374,19 +454,19 @@ const parseFeatureSections = function(parsedContent, featuresToProcess) {
374454 if ( featureExists . type === EntityTypeEnum . PHRASELIST ) {
375455 // de-dupe and add features to intent.
376456 validateFeatureAssignment ( entityType , section . Name , featureExists . type , feature , section . ParseTree . newEntityLine ( ) ) ;
377- addFeatures ( srcEntity , feature , featureTypeEnum . featureToModel , section . ParseTree . newEntityLine ( ) ) ;
457+ addFeatures ( srcEntity , feature , featureTypeEnum . featureToModel , section . ParseTree . newEntityLine ( ) , featureProperties . phraseListFeature ) ;
378458 // set enabledForAllModels on this phrase list
379459 let plEnity = parsedContent . LUISJsonStructure . model_features . find ( item => item . name == feature ) ;
380460 plEnity . enabledForAllModels = false ;
381461 } else {
382462 // de-dupe and add model to intent.
383463 validateFeatureAssignment ( entityType , section . Name , featureExists . type , feature , section . ParseTree . newEntityLine ( ) ) ;
384- addFeatures ( srcEntity , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) ) ;
464+ addFeatures ( srcEntity , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) , featureProperties . entityFeatureToModel [ featureExists . type ] ) ;
385465 }
386466 } else if ( featureIntentExists ) {
387467 // Add intent as a feature to another intent
388468 validateFeatureAssignment ( entityType , section . Name , INTENTTYPE , feature , section . ParseTree . newEntityLine ( ) ) ;
389- addFeatures ( srcEntity , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) ) ;
469+ addFeatures ( srcEntity , feature , featureTypeEnum . modelToFeature , section . ParseTree . newEntityLine ( ) , featureProperties . intentFeatureToModel ) ;
390470 } else {
391471 // Item must be defined before being added as a feature.
392472 let errorMsg = `Features must be defined before assigned to an entity. No definition found for feature "${ feature } " in usesFeature definition for entity "${ section . Name } "` ;
0 commit comments