Controller creates multiple rankers, Surveyor ranks interests from rankers in round-robin fashion

This commit is contained in:
mzhilyaev 2013-11-07 09:55:14 -08:00
Родитель 49d7b97448
Коммит e97d53cb26
3 изменённых файлов: 119 добавлений и 83 удалений

Просмотреть файл

@ -24,8 +24,12 @@ const {Surveyor} = require("Surveyor");
const {storage} = require("sdk/simple-storage");
const simplePrefs = require("simple-prefs")
const kDefaultRankNamespace = "edrules";
const kDefaultRankType = "rules";
const kRankerDefs = [
{type: "rules", namespace: "edrules"},
{type: "combined", namespace: "edrules"},
{type: "rules", namespace: "edrules_extended"},
{type: "combined", namespace: "edrules_extended"},
];
const kDefaultResubmitHistoryDays = 60;
XPCOMUtils.defineLazyServiceGetter(this, "uuid",
@ -34,21 +38,18 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuid",
const kIdleDaily = "idle-daily";
function Controller(options={}) {
let rankNamespace = options.rankNamespace || kDefaultRankNamespace;
let rankType = options.rankType || kDefaultRankType;
let historyDaysToResubmit = options.historyDays || kDefaultResubmitHistoryDays;
let workerFactory = new WorkerFactory();
this._workerFactory = new WorkerFactory();
this._historyDaysToResubmit = historyDaysToResubmit;
this._workers = workerFactory.getCurrentWorkers();
this._ranker = new DayCountRanker(rankNamespace, rankType);
this._workers = this._workerFactory.getCurrentWorkers();
this._rankers = this._makeRankers();
this._annotator = new Annotator();
this._dispatcher = new Dispatcher(simplePrefs.prefs.server_url, {
enabled: simplePrefs.prefs.consented,
dispatchIdleDelay: simplePrefs.prefs.dispatchIdleDelay,
});
this._dayBuffer = new DayBuffer(new Pipeline(this._ranker, this._annotator, this._dispatcher));
this._taxonomy = workerFactory.getTaxonomyInterests(kDefaultRankNamespace);
this._dayBuffer = new DayBuffer(new Pipeline(this._rankers, this._annotator, this._dispatcher));
this._processingHistory = false;
// set up idle-daily observer
@ -70,6 +71,14 @@ Controller.prototype = {
}
},
_makeRankers: function() {
let rankers = [];
kRankerDefs.forEach(def => {
rankers.push(new DayCountRanker(def.namespace, def.type));
});
return rankers;
},
stopAndClearStorage: function() {
// when addon is uninstalled by the Application
// the addonManager seems to unload the code
@ -98,8 +107,10 @@ Controller.prototype = {
storage.lastTimeStamp = 0;
storage.downloadSource = null;
this._dayBuffer.clear();
this._ranker.clear();
this._dispatcher.clear();
this._rankers.forEach(ranker => {
ranker.clear();
});
},
clearStorage: function() {
@ -157,7 +168,9 @@ Controller.prototype = {
resubmitHistory: function(options={}) {
storage.lastTimeStamp = 0;
this._ranker.clear();
this._rankers.forEach(ranker => {
ranker.clear();
});
this._dayBuffer.clear();
this._dayBuffer.setReportCallback(options.report);
return this.submitHistory({daysAgo:this._historyDaysToResubmit, flush:options.flush}).then(() => {
@ -170,11 +183,15 @@ Controller.prototype = {
},
getRankedInterests: function() {
return this._ranker.getRanking();
return this._rankers[0].getRanking();
},
getRankedInterestsForSurvey: function() {
return Surveyor.orderInterestsForSurvey(this.getRankedInterests(), this._taxonomy);
getRankedInterestsForSurvey: function(len=30) {
return Surveyor.orderInterestsForSurvey(
this._rankers.map(ranker => {
return ranker.getRanking();
}),
this._workerFactory.getTaxonomyInterests(kRankerDefs[0].namespace), len);
},
getUserID: function() {

Просмотреть файл

@ -6,60 +6,78 @@ const {Cc,Ci,Cm,Cr,Cu} = require("chrome");
let Surveyor = {
orderInterestsForSurvey: function(scoringInterests, allInterests) {
let orderedInterests = [];
orderInterestsForSurvey: function(rankings, allInterests, fillNb) {
let resultRanking = [];
let orderedRankings = [];
// scoringInterests could be null if history is empty
if (scoringInterests == null) {
scoringInterests = {};
}
Object.keys(scoringInterests).sort(function (a,b) {
return scoringInterests[b] - scoringInterests[a];
}).forEach(it => {
orderedInterests.push({interest: it, score: scoringInterests[it]});
rankings.forEach(ranking => {
let orderedInterests = [];
if (ranking == null) {
ranking = {};
}
Object.keys(ranking).sort(function (a,b) {
return ranking[b] - ranking[a];
}).forEach(interest => {
orderedInterests.push({interest: interest, score: ranking[interest]});
});
orderedRankings.push(orderedInterests);
});
if (orderedInterests.length < 10) {
// simply add randomly picked scoringInterests from taxonomy
// that are not among non-zero scoringInterests. And avoid duplicates
let noDupes = {};
while (orderedInterests.length < 10) {
let index = Math.floor(allInterests.length * Math.random());
let emptyInterest = allInterests[index];
if (scoringInterests[emptyInterest] == null && noDupes[emptyInterest] == null) {
orderedInterests.push({interest: emptyInterest, score: 0});
noDupes[emptyInterest] = 1;
// get first 5 from the first ranking
let usedInterests = {};
let filled = 0;
while (orderedRankings.length &&
filled < 6 &&
filled < fillNb &&
filled < orderedRankings[0].length) {
let interest = orderedRankings[0][filled].interest;
resultRanking.push(orderedRankings[0][filled]);
filled++;
usedInterests[interest] = 1;
}
// now go round-robbin over rankings and choose top interests
// that have not been used yet
let indexes = [];
let len = orderedRankings.length;
for (let i = 0; i < len; i++) {
indexes.push(0);
}
let keepGoing = true;
while (keepGoing && filled < fillNb) {
keepGoing = false;
// loop thourh rest of the ordered rankings
for (let i = 0; i < len; i++) {
let ranking = orderedRankings[i];
if (indexes[i] < ranking.length) {
let item = ranking[indexes[i]];
if (usedInterests[item.interest] == null) {
resultRanking.push(item);
usedInterests[item.interest] = 1;
filled++;
}
indexes[i]++;
keepGoing = true;
}
}
}
else if (orderedInterests.length > 10) {
let delta = Math.round(orderedInterests.length / 3);
let newInterests = [];
// we must choose 4 top, 3 medium and 3 low
// start with top ones
let index = 0;
while (index <= 3) {
newInterests.push(orderedInterests[index++]);
}
// now 3 medium
delta = (delta > 4) ? delta : 4;
index = delta;
while (index < (delta+3)) {
newInterests.push(orderedInterests[index++]);
}
// now 3 low
delta *= 2;
index = delta;
while (index < (delta+3)) {
newInterests.push(orderedInterests[index++]);
}
orderedInterests = newInterests;
// we need to fill the result array with zero scored interests
if (fillNb > allInterests.length) {
fillNb = allInterests.length;
}
return orderedInterests;
while (filled < fillNb) {
let index = Math.floor(allInterests.length * Math.random());
let interest = allInterests[index];
if (usedInterests[interest] == null) {
resultRanking.push({interest: interest, score: 0});
filled++;
usedInterests[interest] = 1;
}
}
return resultRanking;
}
}

Просмотреть файл

@ -22,14 +22,19 @@ exports["test empty profile ranking"] = function test_EmptyProfileRanking(assert
Task.spawn(function() {
try {
yield testUtils.promiseClearHistory();
let testController = new Controller({rankType: "combined"});
let testController = new Controller();
testController.clear()
yield testController.submitHistory({flush: true});
// we should only see 3 urls being processed, hten Autos should nly contain 3 days
assert.ok(testController.getRankedInterests() == null);
// now test how we generate random zero-score interests
let sranked = testController.getRankedInterestsForSurvey();
assert.equal(sranked.length, 10);
assert.equal(sranked.length, 30);
sranked.forEach(pair => {
assert.equal(pair.score,0);
});
sranked = testController.getRankedInterestsForSurvey(50);
sranked.forEach(pair => {
assert.equal(pair.score,0);
});
@ -40,7 +45,6 @@ exports["test empty profile ranking"] = function test_EmptyProfileRanking(assert
}).then(done);
}
exports["test ranking"] = function test_Ranking(assert, done) {
Task.spawn(function() {
try {
@ -60,8 +64,9 @@ exports["test ranking"] = function test_Ranking(assert, done) {
testUtils.isIdentical(assert, testController.getRankedInterests(), {"Autos":4}, "Only Autos");
// now test how we generate random zero-score interests
let sranked = testController.getRankedInterestsForSurvey();
let sranked = testController.getRankedInterestsForSurvey(10);
testUtils.isIdentical(assert, sranked[0] , {"interest":"Autos","score":4}, "first is Autos");
// make sure the rest of scores is zero
let duplicateCatcher = {};
for( let i = 1; i < 10; i++) {
@ -70,7 +75,7 @@ exports["test ranking"] = function test_Ranking(assert, done) {
duplicateCatcher[sranked[i].interest] = 1;
}
let newranks = testController.getRankedInterestsForSurvey();
let newranks = testController.getRankedInterestsForSurvey(10);
testUtils.isIdentical(assert, newranks[0] , {"interest":"Autos","score":4}, "still Autos is first");
// make sure that interetsts are different
@ -90,12 +95,12 @@ exports["test ranking"] = function test_Ranking(assert, done) {
yield testUtils.promiseClearHistory();
let cats = [
{
host: "roughguides.com",
host: "traveler.xyz",
interest: "Travel",
score: 1
},
{
host: "tennisnews.com",
host: "tennis.gr",
interest: "Tennis",
score: 2
},
@ -105,12 +110,12 @@ exports["test ranking"] = function test_Ranking(assert, done) {
score: 3
},
{
host: "autoblog.com",
host: "cars.ru",
interest: "Autos",
score: 4
},
{
host: "cracked.com",
host: "funnyjunk.com",
interest: "Humor",
score: 5
},
@ -125,7 +130,7 @@ exports["test ranking"] = function test_Ranking(assert, done) {
score: 7
},
{
host: "sciencenews.com",
host: "sciencenews.org",
interest: "Science",
score: 8
},
@ -148,7 +153,9 @@ exports["test ranking"] = function test_Ranking(assert, done) {
// make sure that counts stay the same
yield testController.resubmitHistory({flush: true});
sranked = testController.getRankedInterestsForSurvey();
sranked = testController.getRankedInterestsForSurvey(10).sort((a,b) => {
return b.score - a.score;
});
for (let i = 0; i < cats.length; i++) {
assert.equal(cats[9-i].interest, sranked[i].interest, "Interest match");
assert.equal(cats[9-i].score, sranked[i].score, "Score match");
@ -164,23 +171,17 @@ exports["test ranking"] = function test_Ranking(assert, done) {
assert.equal("Gossip", sranked[0].interest);
assert.equal(11, sranked[0].score);
for (let i = 1; i < 6; i++) {
assert.equal(cats[10-i].interest, sranked[i].interest, "Interest match");
assert.equal(cats[10-i].score, sranked[i].score, "Score match");
}
// now add baseball
yield testUtils.addVisits("hardballtimes.com",12,true);
yield testUtils.addVisits("dezeen.com",13,true);
yield testUtils.addVisits("ilounge.com",14,true);
yield testController.resubmitHistory({flush: true});
sranked = testController.getRankedInterestsForSurvey();
testUtils.isIdentical(assert, sranked,
[{"interest":"Apple","score":14},{"interest":"Home-Design","score":13},
{"interest":"Baseball","score":12},{"interest":"Gossip","score":11},
{"interest":"Music","score":9},{"interest":"Science","score":8},{"interest":"Television","score":7},
{"interest":"Autos","score":4},{"interest":"Politics","score":3},{"interest":"Tennis","score":2}],
"Top/Med/Low");
testUtils.isIdentical(assert, sranked[0], {"interest":"Apple","score":14});
testUtils.isIdentical(assert, sranked[1], {"interest":"Home-Design","score":13});
testUtils.isIdentical(assert, sranked[2], {"interest":"Baseball","score":12});
assert.ok(sranked[29] != null);
assert.equal(sranked[29].score, 0);
} catch(ex) {
dump(ex + " ERROROR \n");