Controller creates multiple rankers, Surveyor ranks interests from rankers in round-robin fashion
This commit is contained in:
Родитель
49d7b97448
Коммит
e97d53cb26
|
@ -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() {
|
||||
|
|
110
lib/Surveyor.js
110
lib/Surveyor.js
|
@ -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");
|
||||
|
|
Загрузка…
Ссылка в новой задаче