You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@stanbol.apache.org by og...@apache.org on 2012/02/03 18:39:56 UTC

svn commit: r1240257 - in /incubator/stanbol/trunk/enhancer/engines/topic/src: main/java/org/apache/stanbol/enhancer/engine/topic/ main/java/org/apache/stanbol/enhancer/topic/ main/resources/classifier/ test/java/org/apache/stanbol/enhancer/engine/topic/

Author: ogrisel
Date: Fri Feb  3 17:39:56 2012
New Revision: 1240257

URL: http://svn.apache.org/viewvc?rev=1240257&view=rev
Log:
STANBOL-197: optional store of a primary topic for each skos concept

Modified:
    incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/engine/topic/TopicClassificationEngine.java
    incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicClassifier.java
    incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicSuggestion.java
    incubator/stanbol/trunk/enhancer/engines/topic/src/main/resources/classifier/schema.xml
    incubator/stanbol/trunk/enhancer/engines/topic/src/test/java/org/apache/stanbol/enhancer/engine/topic/TopicEngineTest.java

Modified: incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/engine/topic/TopicClassificationEngine.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/engine/topic/TopicClassificationEngine.java?rev=1240257&r1=1240256&r2=1240257&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/engine/topic/TopicClassificationEngine.java (original)
+++ incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/engine/topic/TopicClassificationEngine.java Fri Feb  3 17:39:56 2012
@@ -111,6 +111,7 @@ import org.slf4j.LoggerFactory;
                      @Property(name = TopicClassificationEngine.LANGUAGES),
                      @Property(name = TopicClassificationEngine.SIMILARTITY_FIELD),
                      @Property(name = TopicClassificationEngine.CONCEPT_URI_FIELD),
+                     @Property(name = TopicClassificationEngine.PRIMARY_TOPIC_URI_FIELD),
                      @Property(name = TopicClassificationEngine.BROADER_FIELD),
                      @Property(name = TopicClassificationEngine.MODEL_UPDATE_DATE_FIELD, value = "last_update_dt"),
                      @Property(name = TopicClassificationEngine.PRECISION_FIELD, value = "precision"),
@@ -143,11 +144,11 @@ public class TopicClassificationEngine e
 
     public static final String SIMILARTITY_FIELD = "org.apache.stanbol.enhancer.engine.topic.similarityField";
 
-    public static final String CONCEPT_URI_FIELD = "org.apache.stanbol.enhancer.engine.topic.uriField";
+    public static final String CONCEPT_URI_FIELD = "org.apache.stanbol.enhancer.engine.topic.conceptUriField";
 
     public static final String BROADER_FIELD = "org.apache.stanbol.enhancer.engine.topic.broaderField";
 
-    public static final String PRIMARY_TOPIC_FIELD = "org.apache.stanbol.enhancer.engine.topic.primaryTopicField";
+    public static final String PRIMARY_TOPIC_URI_FIELD = "org.apache.stanbol.enhancer.engine.topic.primaryTopicField";
 
     public static final String MODEL_UPDATE_DATE_FIELD = "org.apache.stanbol.enhancer.engine.topic.modelUpdateDateField";
 
@@ -205,6 +206,8 @@ public class TopicClassificationEngine e
 
     protected String broaderField;
 
+    protected String primaryTopicUriField;
+
     protected String modelUpdateDateField;
 
     protected String modelEvaluationDateField;
@@ -276,6 +279,7 @@ public class TopicClassificationEngine e
 
         // optional fields, can be null
         broaderField = (String) config.get(BROADER_FIELD);
+        primaryTopicUriField = (String) config.get(PRIMARY_TOPIC_URI_FIELD);
         Object orderParamValue = config.get(ORDER);
         if (orderParamValue != null) {
             order = (Integer) orderParamValue;
@@ -406,13 +410,21 @@ public class TopicClassificationEngine e
             QueryResponse response = request.process(solrServer);
             SolrDocumentList results = response.getResults();
             for (SolrDocument result : results.toArray(new SolrDocument[0])) {
-                String uri = (String) result.getFirstValue(conceptUriField);
-                if (uri == null) {
+                String conceptUri = (String) result.getFirstValue(conceptUriField);
+                if (conceptUri == null) {
                     throw new ClassifierException(String.format(
                         "Solr Core '%s' is missing required field '%s'.", solrCoreId, conceptUriField));
                 }
                 Float score = (Float) result.getFirstValue("score");
-                suggestedTopics.add(new TopicSuggestion(uri, score));
+
+                // fetch metadata
+                String q = entryTypeField + ":" + METADATA_ENTRY + " AND " + conceptUriField + ":" + ClientUtils.escapeQueryChars(conceptUri);
+                SolrQuery metadataQuery = new SolrQuery(q);
+                metadataQuery.setFields(conceptUriField, broaderField, primaryTopicUriField);
+                SolrDocument metadata = solrServer.query(metadataQuery).getResults().get(0);
+                String primaryTopicUri = (String) metadata.getFirstValue(primaryTopicUriField);
+                suggestedTopics.add(new TopicSuggestion(conceptUri, primaryTopicUri, metadata
+                        .getFieldValues(broaderField), score));
             }
         } catch (SolrServerException e) {
             if ("unknown handler: /mlt".equals(e.getCause().getMessage())) {
@@ -533,23 +545,26 @@ public class TopicClassificationEngine e
     }
 
     @Override
-    public void addConcept(String conceptId, Collection<String> broaderConcepts) throws ClassifierException {
+    public void addConcept(String conceptUri, String primaryTopicUri, Collection<String> broaderConcepts) throws ClassifierException {
         // ensure that there is no previous topic registered with the same id
-        removeConcept(conceptId);
+        removeConcept(conceptUri);
 
         SolrInputDocument metadataEntry = new SolrInputDocument();
         String metadataEntryId = UUID.randomUUID().toString();
         String modelEntryId = UUID.randomUUID().toString();
-        metadataEntry.addField(conceptUriField, conceptId);
+        metadataEntry.addField(conceptUriField, conceptUri);
         metadataEntry.addField(entryIdField, metadataEntryId);
         metadataEntry.addField(modelEntryIdField, modelEntryId);
         metadataEntry.addField(entryTypeField, METADATA_ENTRY);
         if (broaderConcepts != null && broaderField != null) {
             metadataEntry.addField(broaderField, broaderConcepts);
         }
+        if (primaryTopicUri != null && primaryTopicUriField != null) {
+            metadataEntry.addField(primaryTopicUriField, primaryTopicUri);
+        }
         SolrInputDocument modelEntry = new SolrInputDocument();
         modelEntry.addField(entryIdField, modelEntryId);
-        modelEntry.addField(conceptUriField, conceptId);
+        modelEntry.addField(conceptUriField, conceptUri);
         modelEntry.addField(entryTypeField, MODEL_ENTRY);
         if (broaderConcepts != null) {
             invalidateModelFields(broaderConcepts, modelUpdateDateField, modelEvaluationDateField);
@@ -562,12 +577,17 @@ public class TopicClassificationEngine e
             solrServer.request(request);
             solrServer.commit();
         } catch (Exception e) {
-            String msg = String.format("Error adding topic with id '%s' on Solr Core '%s'", conceptId,
+            String msg = String.format("Error adding topic with id '%s' on Solr Core '%s'", conceptUri,
                 solrCoreId);
             throw new ClassifierException(msg, e);
         }
     }
 
+    @Override
+    public void addConcept(String conceptId, Collection<String> broaderConcepts) throws ClassifierException {
+        addConcept(conceptId, null, broaderConcepts);
+    }
+
     /*
      * The commit is the responsibility of the caller.
      */
@@ -698,7 +718,11 @@ public class TopicClassificationEngine e
                     }
                     String metadataEntryId = result.getFirstValue(entryIdField).toString();
                     String modelEntryId = result.getFirstValue(modelEntryIdField).toString();
-                    updateTopic(conceptId, metadataEntryId, modelEntryId, impactedTopics,
+                    String primaryTopicUri = null;
+                    if (primaryTopicUriField != null) {
+                        primaryTopicUri = (String) result.getFirstValue(primaryTopicUriField);
+                    }
+                    updateTopic(conceptId, metadataEntryId, modelEntryId, impactedTopics, primaryTopicUri,
                         result.getFieldValues(broaderField));
                     processed++;
                 }
@@ -711,7 +735,7 @@ public class TopicClassificationEngine e
     }
 
     /**
-     * @param conceptId
+     * @param conceptUri
      *            the topic model to update
      * @param metadataEntryId
      *            of the metadata entry id of the topic
@@ -719,15 +743,17 @@ public class TopicClassificationEngine e
      *            of the model entry id of the topic
      * @param impactedTopics
      *            the list of impacted topics (e.g. the topic node and direct children)
+     * @param primaryTopicUri 
      * @param broaderConcepts
      *            the collection of broader to re-add in the broader field
      */
-    protected void updateTopic(String conceptId,
+    protected void updateTopic(String conceptUri,
                                String metadataId,
                                String modelId,
                                List<String> impactedTopics,
+                               String primaryTopicUri,
                                Collection<Object> broaderConcepts) throws TrainingSetException,
-                                                                ClassifierException {
+                                                                  ClassifierException {
         long start = System.currentTimeMillis();
         Batch<Example> examples = Batch.emtpyBatch(Example.class);
         StringBuffer sb = new StringBuffer();
@@ -750,7 +776,7 @@ public class TopicClassificationEngine e
         // reindex the topic with the new text data collected from the examples
         SolrInputDocument modelEntry = new SolrInputDocument();
         modelEntry.addField(entryIdField, modelId);
-        modelEntry.addField(conceptUriField, conceptId);
+        modelEntry.addField(conceptUriField, conceptUri);
         modelEntry.addField(entryTypeField, MODEL_ENTRY);
         if (sb.length() > 0) {
             modelEntry.addField(similarityField, sb);
@@ -761,7 +787,10 @@ public class TopicClassificationEngine e
         metadataEntry.addField(entryIdField, metadataId);
         metadataEntry.addField(modelEntryIdField, modelId);
         metadataEntry.addField(entryTypeField, METADATA_ENTRY);
-        metadataEntry.addField(conceptUriField, conceptId);
+        metadataEntry.addField(conceptUriField, conceptUri);
+        if (primaryTopicUriField != null) {
+            metadataEntry.addField(primaryTopicUriField, primaryTopicUri);
+        }
         if (broaderConcepts != null && broaderField != null) {
             metadataEntry.addField(broaderField, broaderConcepts);
         }
@@ -776,12 +805,12 @@ public class TopicClassificationEngine e
             solrServer.request(request);
             // the commit is done by the caller in batch
         } catch (Exception e) {
-            String msg = String.format("Error updating topic with id '%s' on Solr Core '%s'", conceptId,
+            String msg = String.format("Error updating topic with id '%s' on Solr Core '%s'", conceptUri,
                 solrCoreId);
             throw new ClassifierException(msg, e);
         }
         long stop = System.currentTimeMillis();
-        log.debug("Sucessfully updated topic {} in {}s", conceptId, (double) (stop - start) / 1000.);
+        log.debug("Sucessfully updated topic {} in {}s", conceptUri, (double) (stop - start) / 1000.);
     }
 
     protected void checkTrainingSet() throws TrainingSetException {
@@ -809,7 +838,8 @@ public class TopicClassificationEngine e
         config.put(TopicClassificationEngine.ENTRY_TYPE_FIELD, "entry_type");
         config.put(TopicClassificationEngine.MODEL_ENTRY_ID_FIELD, "model_entry_id");
         config.put(TopicClassificationEngine.SOLR_CORE, server);
-        config.put(TopicClassificationEngine.CONCEPT_URI_FIELD, "topic");
+        config.put(TopicClassificationEngine.CONCEPT_URI_FIELD, "concept");
+        config.put(TopicClassificationEngine.PRIMARY_TOPIC_URI_FIELD, "primary_topic");
         config.put(TopicClassificationEngine.SIMILARTITY_FIELD, "classifier_features");
         config.put(TopicClassificationEngine.BROADER_FIELD, "broader");
         config.put(TopicClassificationEngine.MODEL_UPDATE_DATE_FIELD, "last_update_dt");
@@ -882,13 +912,13 @@ public class TopicClassificationEngine e
                     String conceptId = topicEntry.getFirstValue(conceptUriField).toString();
                     Collection<Object> broader = topicEntry.getFieldValues(broaderField);
                     if (broader == null) {
-                        classifier.addConcept(conceptId, null);
+                        classifier.addConcept(conceptId, null, null);
                     } else {
                         List<String> broaderConcepts = new ArrayList<String>();
                         for (Object broaderConcept : broader) {
                             broaderConcepts.add(broaderConcept.toString());
                         }
-                        classifier.addConcept(conceptId, broaderConcepts);
+                        classifier.addConcept(conceptId, null, broaderConcepts);
                     }
                 }
                 return batch.size();
@@ -1040,8 +1070,9 @@ public class TopicClassificationEngine e
                 solrServer.add(newEntry);
             }
         } catch (Exception e) {
-            String msg = String.format(
-                "Error updating performance metadata for topic '%s' on Solr Core '%s'", conceptId, solrCoreId);
+            String msg = String
+                    .format("Error updating performance metadata for topic '%s' on Solr Core '%s'",
+                        conceptId, solrCoreId);
             throw new ClassifierException(msg, e);
         }
     }

Modified: incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicClassifier.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicClassifier.java?rev=1240257&r1=1240256&r2=1240257&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicClassifier.java (original)
+++ incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicClassifier.java Fri Feb  3 17:39:56 2012
@@ -25,8 +25,8 @@ import org.apache.stanbol.enhancer.topic
 import org.apache.stanbol.enhancer.topic.training.TrainingSetException;
 
 /**
- * Service interface for suggesting hierarchical concepts from a specific scheme (a.k.a. taxonomy, thesaurus or
- * concepts hierarchy) from the text content of a document or part of a document.
+ * Service interface for suggesting hierarchical concepts from a specific scheme (a.k.a. taxonomy, thesaurus
+ * or concepts hierarchy) from the text content of a document or part of a document.
  */
 public interface TopicClassifier {
 
@@ -69,8 +69,8 @@ public interface TopicClassifier {
     Set<String> getRootConcepts() throws ClassifierException;
 
     /**
-     * @return true if the classifier model can be updated with the {@code addConcept} / {@code removeConcept} /
-     *         {@code updateModel} / methods.
+     * @return true if the classifier model can be updated with the {@code addConcept} / {@code removeConcept}
+     *         / {@code updateModel} / methods.
      */
     boolean isUpdatable();
 
@@ -79,12 +79,26 @@ public interface TopicClassifier {
      * can delete the underlying statistical model. Calling {@code updateModel} is necessary to rebuild the
      * statistical model based on the hierarchical structure of the concepts and the registered training set.
      * 
-     * @param id
-     *            the new topic id
+     * @param conceptUri
+     *            the new concept identifier
+     * @param primaryTopicUri
+     *            optional identifier of a resource best describing that concept.
      * @param broaderConcepts
      *            list of directly broader concepts in the thesaurus
      */
-    void addConcept(String id, Collection<String> broaderConcepts) throws ClassifierException;
+    void addConcept(String conceptUri, String primaryTopicUri, Collection<String> broaderConcepts) throws ClassifierException;
+
+    /**
+     * Register a topic and set it's ancestors in the taxonomy. Warning: re-adding an already existing topic
+     * can delete the underlying statistical model. Calling {@code updateModel} is necessary to rebuild the
+     * statistical model based on the hierarchical structure of the concepts and the registered training set.
+     * 
+     * @param conceptUri
+     *            the new concept identifier
+     * @param broaderConcepts
+     *            list of directly broader concepts in the thesaurus
+     */
+    void addConcept(String conceptUri, Collection<String> broaderConcepts) throws ClassifierException;
 
     /**
      * Remove a topic from the thesaurus. WARNING: it is the caller responsibility to recursively remove or
@@ -92,10 +106,10 @@ public interface TopicClassifier {
      * {@code updateModel} should be called to re-align the statistical model to match the new hierarchy by
      * drawing examples from the dataset.
      * 
-     * @param id
+     * @param conceptUri
      *            if of the topic to remove from the model
      */
-    void removeConcept(String id) throws ClassifierException;
+    void removeConcept(String conceptUri) throws ClassifierException;
 
     /**
      * Register a training set to use to build the statistical model of the classifier.

Modified: incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicSuggestion.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicSuggestion.java?rev=1240257&r1=1240256&r2=1240257&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicSuggestion.java (original)
+++ incubator/stanbol/trunk/enhancer/engines/topic/src/main/java/org/apache/stanbol/enhancer/topic/TopicSuggestion.java Fri Feb  3 17:39:56 2012
@@ -17,6 +17,7 @@
 package org.apache.stanbol.enhancer.topic;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 import org.apache.commons.lang.StringUtils;
@@ -48,11 +49,16 @@ public class TopicSuggestion {
      */
     public final float score;
 
-    public TopicSuggestion(String conceptUri, String primaryTopicUri, List<String> broader, float score) {
+    public TopicSuggestion(String conceptUri,
+                           String primaryTopicUri,
+                           Collection<? extends Object> broader,
+                           float score) {
         this.conceptUri = conceptUri;
         this.primaryTopicUri = primaryTopicUri;
         if (broader != null) {
-            this.broader.addAll(broader);
+            for (Object broaderConcept : broader) {
+                this.broader.add(broaderConcept.toString());
+            }
         }
         this.score = score;
     }

Modified: incubator/stanbol/trunk/enhancer/engines/topic/src/main/resources/classifier/schema.xml
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/engines/topic/src/main/resources/classifier/schema.xml?rev=1240257&r1=1240256&r2=1240257&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/engines/topic/src/main/resources/classifier/schema.xml (original)
+++ incubator/stanbol/trunk/enhancer/engines/topic/src/main/resources/classifier/schema.xml Fri Feb  3 17:39:56 2012
@@ -62,7 +62,7 @@
       required="true" />
 
     <!-- Mandatory field for all entries: this is the logical primary key -->
-    <field name="topic" type="string" indexed="true" stored="true"
+    <field name="concept" type="string" indexed="true" stored="true"
       required="true" />
 
     <!-- If entry_type can be model 'model' or 'metadata' -->
@@ -77,6 +77,7 @@
     <!-- Classifier model stored attributes when entry_type == 'metadata' -->
     <field name="model_entry_id" type="string" indexed="true"
       stored="true" />
+    <field name="primary_topic" type="string" indexed="true" stored="true" />
     <field name="broader" type="string" indexed="true" stored="true"
       multiValued="true" />
     <field name="last_update_dt" type="tdate" indexed="true"

Modified: incubator/stanbol/trunk/enhancer/engines/topic/src/test/java/org/apache/stanbol/enhancer/engine/topic/TopicEngineTest.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/engines/topic/src/test/java/org/apache/stanbol/enhancer/engine/topic/TopicEngineTest.java?rev=1240257&r1=1240256&r2=1240257&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/engines/topic/src/test/java/org/apache/stanbol/enhancer/engine/topic/TopicEngineTest.java (original)
+++ incubator/stanbol/trunk/enhancer/engines/topic/src/test/java/org/apache/stanbol/enhancer/engine/topic/TopicEngineTest.java Fri Feb  3 17:39:56 2012
@@ -19,6 +19,7 @@ package org.apache.stanbol.enhancer.engi
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -122,7 +123,7 @@ public class TopicEngineTest extends Emb
         assertNotNull(classifier);
         assertEquals(classifier.engineId, "test-engine");
         assertEquals(classifier.getActiveSolrServer(), classifierSolrServer);
-        assertEquals(classifier.conceptUriField, "topic");
+        assertEquals(classifier.conceptUriField, "concept");
         assertEquals(classifier.similarityField, "classifier_features");
         assertEquals(classifier.acceptedLanguages, new ArrayList<String>());
 
@@ -220,8 +221,8 @@ public class TopicEngineTest extends Emb
     public void testTrainClassifierFromExamples() throws Exception {
 
         // mini taxonomy for news articles
-        String business = "urn:topics/business";
-        String technology = "urn:topics/technology";
+        String[] business = {"urn:topics/business", "http://dbpedia.org/resource/Business"};
+        String[] technology = {"urn:topics/technology", "http://dbpedia.org/resource/Technology"};
         String apple = "urn:topics/apple";
         String sport = "urn:topics/sport";
         String football = "urn:topics/football";
@@ -229,11 +230,11 @@ public class TopicEngineTest extends Emb
         String music = "urn:topics/music";
         String law = "urn:topics/law";
 
-        classifier.addConcept(business, null);
-        classifier.addConcept(technology, null);
+        classifier.addConcept(business[0], business[1], null);
+        classifier.addConcept(technology[0], technology[1], null);
         classifier.addConcept(sport, null);
         classifier.addConcept(music, null);
-        classifier.addConcept(apple, Arrays.asList(business, technology));
+        classifier.addConcept(apple, Arrays.asList(business[0], technology[0]));
         classifier.addConcept(football, Arrays.asList(sport));
         classifier.addConcept(worldcup, Arrays.asList(football));
 
@@ -255,13 +256,13 @@ public class TopicEngineTest extends Emb
         trainingSet.registerExample(null, "Money, money, money is the root of all evil." + STOP_WORDS,
             Arrays.asList(business));
         trainingSet.registerExample(null, "VC invested more money in tech startups in 2011." + STOP_WORDS,
-            Arrays.asList(business, technology));
+            Arrays.asList(business[0], technology[0]));
 
         trainingSet.registerExample(null, "Apple's iPad is a small handheld computer with a touch screen UI"
-                                          + STOP_WORDS, Arrays.asList(apple, technology));
+                                          + STOP_WORDS, Arrays.asList(apple, technology[0]));
         trainingSet.registerExample(null, "Apple sold the iPad at a very high price"
                                           + " and made record profits." + STOP_WORDS,
-            Arrays.asList(apple, business));
+            Arrays.asList(apple, business[0]));
 
         trainingSet.registerExample(null, "Manchester United won 3-2 against FC Barcelona." + STOP_WORDS,
             Arrays.asList(football));
@@ -292,8 +293,14 @@ public class TopicEngineTest extends Emb
         suggestions = classifier.suggestTopics("Apple is no longer a startup.");
         assertTrue(suggestions.size() >= 3);
         assertEquals(apple, suggestions.get(0).conceptUri);
-        assertEquals(technology, suggestions.get(1).conceptUri);
-        assertEquals(business, suggestions.get(2).conceptUri);
+        assertNull(suggestions.get(0).primaryTopicUri);
+        assertEquals(Arrays.asList(business[0], technology[0]), suggestions.get(0).broader);
+
+        assertEquals(technology[0], suggestions.get(1).conceptUri);
+        assertEquals(technology[1], suggestions.get(1).primaryTopicUri);
+
+        assertEquals(business[0], suggestions.get(2).conceptUri);
+        assertEquals(business[1], suggestions.get(2).primaryTopicUri);
 
         suggestions = classifier.suggestTopics("You can watch the worldcup on your iPad.");
         assertTrue(suggestions.size() >= 2);
@@ -336,10 +343,9 @@ public class TopicEngineTest extends Emb
         assertEquals(0, classifier.updateModel(true));
 
         // registering new subtopics invalidate the models of the parent as well
-        classifier.addConcept("urn:topics/sportsmafia", Arrays.asList(football, business));
+        classifier.addConcept("urn:topics/sportsmafia", Arrays.asList(football, business[0]));
         assertEquals(3, classifier.updateModel(true));
         assertEquals(0, classifier.updateModel(true));
-
     }
 
     @Test
@@ -521,7 +527,8 @@ public class TopicEngineTest extends Emb
         config.put(TopicClassificationEngine.ENTRY_TYPE_FIELD, "entry_type");
         config.put(TopicClassificationEngine.MODEL_ENTRY_ID_FIELD, "model_entry_id");
         config.put(TopicClassificationEngine.SOLR_CORE, classifierSolrServer);
-        config.put(TopicClassificationEngine.CONCEPT_URI_FIELD, "topic");
+        config.put(TopicClassificationEngine.CONCEPT_URI_FIELD, "concept");
+        config.put(TopicClassificationEngine.PRIMARY_TOPIC_URI_FIELD, "primary_topic");
         config.put(TopicClassificationEngine.SIMILARTITY_FIELD, "classifier_features");
         config.put(TopicClassificationEngine.BROADER_FIELD, "broader");
         config.put(TopicClassificationEngine.MODEL_UPDATE_DATE_FIELD, "last_update_dt");