Commit 0299c626 by Иван Кубота

new tag based sequence generator

parent 2ea85a78
Pipeline #566 canceled with stage
...@@ -77,14 +77,14 @@ quizTypes.checkbox = [ ComponentsOfProduct = { ...@@ -77,14 +77,14 @@ quizTypes.checkbox = [ ComponentsOfProduct = {
type: 'Select components of product', type: 'Select components of product',
products: {from: 2, to: 4}, products: {from: 2, to: 4},
minSimilarTags: 1, minSimilarTags: 1,
from(log){ from(log, isTest){
const products = qB.randomProduct({ const products = qB.randomProduct({
minComponents: 1, minComponents: 1,
amount: this.products, amount: this.products,
connectedByTags: true, connectedByTags: true,
minSimilarTags: this.minSimilarTags, minSimilarTags: this.minSimilarTags,
doNotTrim: true doNotTrim: true
}, log), }, log, isTest),
componentsWithProducts = qB.getComponentsWithSharedProducts(products, 2); componentsWithProducts = qB.getComponentsWithSharedProducts(products, 2);
......
...@@ -7,7 +7,7 @@ const qB = { ...@@ -7,7 +7,7 @@ const qB = {
minSimilarTags, minSimilarTags,
doNotTrim, doNotTrim,
withPhoto withPhoto
}, log) { }, log, isTest) {
let filtered = Object.values(dP.products); let filtered = Object.values(dP.products);
...@@ -74,7 +74,7 @@ const qB = { ...@@ -74,7 +74,7 @@ const qB = {
similarClusters = Object.keys(similarTags) similarClusters = Object.keys(similarTags)
.map(k=>({k, v:similarTags[k]})) .map(k=>({k, v:similarTags[k]}))
.filter( a => a.v.length >= minAmount && a.v.filter(av=>av.use).length>0); .filter( a => a.v.length >= minAmount && a.v.filter(isTest).length>0);
log.push(`Clusters that have >= ${minSimilarTags} similar tags: `+Object.values(similarClusters).length); log.push(`Clusters that have >= ${minSimilarTags} similar tags: `+Object.values(similarClusters).length);
...@@ -89,7 +89,7 @@ const qB = { ...@@ -89,7 +89,7 @@ const qB = {
let result; let result;
if(single){ if(single){
result = rand(filtered.filter(p=>p.use)); result = rand(filtered.filter(isTest));
log.push('Matched product: '+result.title); log.push('Matched product: '+result.title);
}else{ }else{
...@@ -98,7 +98,7 @@ const qB = { ...@@ -98,7 +98,7 @@ const qB = {
return false; return false;
} }
let used = rand(filtered.filter(p=>p.use)), let used = rand(filtered.filter(isTest)),
other = filtered.slice(); other = filtered.slice();
other.splice(other.indexOf(used)); other.splice(other.indexOf(used));
...@@ -192,11 +192,11 @@ const qB = { ...@@ -192,11 +192,11 @@ const qB = {
prebuild: { prebuild: {
similarTaggedProductWithPhotoAndComponents: { similarTaggedProductWithPhotoAndComponents: {
fn: function(log) { fn: function(log, isTest) {
this.getEmAll = true; this.getEmAll = true;
this.products = {min: 2}; this.products = {min: 2};
const possibilities = shuffle( const possibilities = shuffle(
qB.prebuild.similarTaggedProductWithPhoto.fn.call(this, log) qB.prebuild.similarTaggedProductWithPhoto.fn.call(this, log, isTest)
); );
if(!possibilities || !possibilities.length) if(!possibilities || !possibilities.length)
return false; return false;
...@@ -238,7 +238,7 @@ const qB = { ...@@ -238,7 +238,7 @@ const qB = {
questionMinComponentsCount: Number, questionMinComponentsCount: Number,
products: {min: Number, max: Number}, products: {min: Number, max: Number},
fn: function(log) { fn: function(log, isTest) {
let allProducts = Object.values(dP.products); let allProducts = Object.values(dP.products);
let products = Object.values(dP.products) let products = Object.values(dP.products)
.filter(p=>p.image) .filter(p=>p.image)
...@@ -251,7 +251,7 @@ const qB = { ...@@ -251,7 +251,7 @@ const qB = {
} }
const possibility = products.map(p=>{ const possibility = products.map(p=>{
if(!p.use) if(!isTest(p))
return false; return false;
const similar = qB.getProductsWithTags({ const similar = qB.getProductsWithTags({
products: allProducts, products: allProducts,
...@@ -285,20 +285,20 @@ const qB = { ...@@ -285,20 +285,20 @@ const qB = {
// product similarity (minimal matched tags count) // product similarity (minimal matched tags count)
minSimilarTags: 2, minSimilarTags: 2,
fn(log){ fn(log, isTest){
let products = qB.randomProduct({ let products = qB.randomProduct({
minComponents: this.questionCmpAmount, minComponents: this.questionCmpAmount,
amount: this.products, amount: this.products,
connectedByTags: true, connectedByTags: true,
minSimilarTags: this.minSimilarTags minSimilarTags: this.minSimilarTags
}, log); }, log, isTest);
for( let i = 0, _i = products.length; i < _i; i++ ){ for( let i = 0, _i = products.length; i < _i; i++ ){
const product = products[ i ]; const product = products[ i ];
/* if(this.withPhoto && !product.image) /* if(this.withPhoto && !product.image)
continue;*/ continue;*/
if(!product.use) if(!isTest(product))
continue; continue;
const cmps = product const cmps = product
.getComponents() .getComponents()
......
...@@ -88,8 +88,8 @@ quizTypes.radio = [ ...@@ -88,8 +88,8 @@ quizTypes.radio = [
question: ({product})=> `Выберите лишний ингредиент, НЕ входящий в продукт "${product.title}"`, question: ({product})=> `Выберите лишний ингредиент, НЕ входящий в продукт "${product.title}"`,
type: 'Do not contain', type: 'Do not contain',
from: (log)=>{ from: (log, isTest)=>{
const product = qB.randomProduct({ minComponents: 2, single: true }, log); const product = qB.randomProduct({ minComponents: 2, single: true }, log, isTest);
return {baseProduct: product, product} return {baseProduct: product, product}
}, },
......
...@@ -81,10 +81,10 @@ const standardGenerator = function(random) { ...@@ -81,10 +81,10 @@ const standardGenerator = function(random) {
log log
}; };
}; };
const quizGenerator = function(type, photo, subType) { const quizGenerator = function(type, photo, isTest, subType) {
return _quizGenerator(type, photo, subType); return _quizGenerator(type, photo, isTest, subType);
}, },
_quizGenerator = function(type, photo, subType, attempt) { _quizGenerator = function(type, photo, isTest, subType, attempt) {
attempt = attempt || 0; attempt = attempt || 0;
const initialSeed = Math.random.seeded.getStringSeed(); const initialSeed = Math.random.seeded.getStringSeed();
let log; let log;
...@@ -119,23 +119,23 @@ const quizGenerator = function(type, photo, subType) { ...@@ -119,23 +119,23 @@ const quizGenerator = function(type, photo, subType) {
if(!cfg) if(!cfg)
return {answers: [], log}; return {answers: [], log};
const source = cfg.from.call(cfg, log); const source = cfg.from.call(cfg, log, isTest);
if(source === false){ if(source === false){
// давай по новой // давай по новой
return _quizGenerator( type, photo, subType, attempt + 1 ); return _quizGenerator( type, photo, isTest, subType, attempt + 1 );
} }
let answers = shuffle(cfg.answer.call(cfg, source, log)); let answers = shuffle(cfg.answer.call(cfg, source, log, isTest));
if(answers === false || answers.length === 1){ if(answers === false || answers.length === 1){
// давай по новой // давай по новой
return _quizGenerator( type, photo, subType, attempt + 1 ); return _quizGenerator( type, photo, isTest, subType, attempt + 1 );
} }
return { return {
seed: initialSeed, seed: initialSeed,
type, type,
categoryId: 1, categoryId: 1,
productId: source.baseProduct.id, productId: source.baseProduct.id,
question: cfg.question.call(cfg, source, log), question: cfg.question.call(cfg, source, log, isTest),
answers, answers,
image: !photo?null:source.baseProduct.image, image: !photo?null:source.baseProduct.image,
......
...@@ -32,7 +32,7 @@ var js = sources.map(a=>path.join(__dirname,'../../public',a)).map(n=>fs.readFil ...@@ -32,7 +32,7 @@ var js = sources.map(a=>path.join(__dirname,'../../public',a)).map(n=>fs.readFil
let data = require("../../db.js"); let data = require("../../db.js");
const body = 'const window = {}, localStorage = {getItem:()=>"{}"};'+js+'; return {quizGenerator, standardGenerator, quizTypes, probabilityRand, initDataProvider, seeded: Math.random.seeded, rand, dP}'; const body = 'const window = {}, localStorage = {getItem:()=>"{}"};'+js+'; return {shuffle, quizGenerator, standardGenerator, quizTypes, probabilityRand, initDataProvider, seeded: Math.random.seeded, rand, dP}';
var ctxCtor = new Function('',body); var ctxCtor = new Function('',body);
...@@ -350,10 +350,12 @@ module.exports = { ...@@ -350,10 +350,12 @@ module.exports = {
connections: [], connections: [],
products: {}, products: {},
components: {}, components: {},
standardQuestions: {} standardQuestions: {},
category: {}
}; };
var cmpID = 1; var cmpID = 1;
l.forEach( category => { l.forEach( category => {
mimicri.category[category.id] = {name: category.name, title: category.title, type: category.type, id: category.id};
if( category.type === 1 && !category.hidden ){ if( category.type === 1 && !category.hidden ){
category.cards.forEach( card => { category.cards.forEach( card => {
card.components.forEach( cmp => { card.components.forEach( cmp => {
...@@ -557,7 +559,8 @@ module.exports = { ...@@ -557,7 +559,8 @@ module.exports = {
nolog: {required: false, description: 'remove human readable log', type: Boolean}, nolog: {required: false, description: 'remove human readable log', type: Boolean},
stats: {required: false, description: 'aggregate stats and explain questions', type: Boolean}, stats: {required: false, description: 'aggregate stats and explain questions', type: Boolean},
human: {required: false, description: 'aggregate stats and explain questions', type: Boolean}, human: {required: false, description: 'aggregate stats and explain questions', type: Boolean},
category: {required: false, description: 'comma sepparated categories', type: String}, category: {required: false, description: 'comma separated categories', type: String},
tags: {required: false, description: 'comma separated tags', type: Array},
}, },
fn: async function(args, req, res) { fn: async function(args, req, res) {
await ctx.ready; const rand = ctx.rand, seeded = ctx.seeded; await ctx.ready; const rand = ctx.rand, seeded = ctx.seeded;
...@@ -581,10 +584,104 @@ module.exports = { ...@@ -581,10 +584,104 @@ module.exports = {
globalMaxTries = args.count*500, globalMaxTries = args.count*500,
globalTries = 0; globalTries = 0;
if(args.count>100)args.count = 100; if(args.count>100)args.count = 100;
while(generated<args.count && globalTries < globalMaxTries){
globalTries++;
if(args.tags){
let tags = args.tags.map(t=>ctx.dP.tagsHash[t]);
let info = {};
let isTest = {};
let isTestFn = (p)=>{
let c = p.category_id, id = p.id;
return isTest[c] && isTest[c][id];
};
let productTagCount = 0,
pCount = 0;
tags.forEach(t=>{
if(t.type_id === 7){
let category = ctx.dP.category[t.category_id];
if(category.type === 2){
info[ category.id ] = true;
}else{
if(!(category.id in isTest))
isTest[category.id] = {};
Object.values(ctx.dP.products)
.filter(p=>p.category_id === category.id)
.forEach(c=>isTest[category.id][c.id] = true);
}
}else if(t.type_id === 13){
productTagCount++;
Object.values(ctx.dP.products)
.filter(p=>p.tags && p.tags.indexOf(t.id)>-1)
.forEach(p=>{
if(!(p.category_id in isTest))
isTest[p.category_id] = {};
pCount++;
isTest[p.category_id][p.id] = true;
});
}
});
// СФОРМИРОВАЛИ ОЖИДАНИЮ
let queues = {};
Object.keys(info).forEach(k=>{
k=parseInt(k,10);
queues[k] = ctx.shuffle(Object.values( data.standardQuestions )
.filter(q=>q.category_id === k)
.map( ( q ) => q.qID ));
});
let groups = [];
Object.keys(queues).forEach(k=>{
groups.push({type: 2, id: k, count: queues[k].length});
});
for(let i = 0; i <productTagCount; i++){
groups.push({type: 1, count: pCount});
}
let step = groups.slice();
while( generated < args.count && globalTries < globalMaxTries ){
let result;
globalTries++;
let take = ctx.rand(step);
if(take.type === 2){
let took = queues[take.id].pop();
take.count--;
if(!take.count){
groups.splice(groups.indexOf(take),1);
if(!groups.length)
break;
}
result = ctx.standardGenerator( () => took );
}else{
let option = ctx.probabilityRand([].concat.apply([],Object.values(ctx.quizTypes))) let option = ctx.probabilityRand([].concat.apply([],Object.values(ctx.quizTypes)))
let multiple = option.multiple,
photo = option.photo;
result = ctx.quizGenerator(
multiple ? 'checkbox' : 'radio',
args.photo !== void 0 ? args.photo : photo,
isTestFn
);
}
if(result){
list.push( result );
generated++;
}
step = groups.length>1?groups.slice().filter(i=>i!== take): groups.slice();
}
// new generator;
}else{
while( generated < args.count && globalTries < globalMaxTries ){
globalTries++;
let option = ctx.probabilityRand( [].concat.apply( [], Object.values( ctx.quizTypes ) ) )
let multiple = option.multiple,//seeded() > 24 / ( 46 + 24 ), let multiple = option.multiple,//seeded() > 24 / ( 46 + 24 ),
photo = option.photo; //seeded() > 0.7 photo = option.photo; //seeded() > 0.7
...@@ -594,7 +691,7 @@ module.exports = { ...@@ -594,7 +691,7 @@ module.exports = {
p: photo,//seeded() > 0.7, p: photo,//seeded() > 0.7,
c: 1 c: 1
}; };
if( (useCategory && !categoryHash[1]) || seeded() > 0.3 ){ if( ( useCategory && !categoryHash[ 1 ] ) || seeded() > 0.3 ){
//if( last.c === 1 && seeded() > 0.5 ){ //if( last.c === 1 && seeded() > 0.5 ){
cur = { cur = {
c: 2 c: 2
...@@ -607,7 +704,7 @@ module.exports = { ...@@ -607,7 +704,7 @@ module.exports = {
break; break;
} }
}while('2.'+id in used);*/ }while('2.'+id in used);*/
if(tries===100) if( tries === 100 )
continue; continue;
cur.id = id; cur.id = id;
} }
...@@ -615,19 +712,19 @@ module.exports = { ...@@ -615,19 +712,19 @@ module.exports = {
let tries = 0, let tries = 0,
result; result;
do{ do{
if(cur.c === 1){ if( cur.c === 1 ){
result = ctx.quizGenerator( cur.m ? 'checkbox' : 'radio', cur.p ); result = ctx.quizGenerator( cur.m ? 'checkbox' : 'radio', cur.p );
}else{ }else{
let counter = 0, r = 2; let counter = 0, r = 2;
if(useCategory){ if( useCategory ){
let possible = Object.values(data.standardQuestions) let possible = Object.values( data.standardQuestions )
.map( ( q ) => ( { q, c: q.category_id, n: q.qID } ) ) .map( ( q ) => ( { q, c: q.category_id, n: q.qID } ) )
.filter( q => q.c in categoryHash ) .filter( q => q.c in categoryHash )
.filter( q => !(q.c +'.'+q.n in used) ) .filter( q => !( q.c + '.' + q.n in used ) )
.map( q => q.n ); .map( q => q.n );
if(possible.length){ if( possible.length ){
r = rand( possible ) r = rand( possible )
}else{ }else{
error = 'Not enough questions'; error = 'Not enough questions';
...@@ -644,33 +741,39 @@ module.exports = { ...@@ -644,33 +741,39 @@ module.exports = {
}while( counter < 100 ); }while( counter < 100 );
} }
result = ctx.standardGenerator(()=>r); result = ctx.standardGenerator( () => r );
if(!result){ if( !result ){
tries = 100; tries = 100;
break; break;
} }
} }
tries++; tries++;
if(tries === 100) if( tries === 100 )
break; break;
}while(result.categoryId+'.'+result.productId in used || ((cur.c>1 && result.categoryId === last.c)&& !useCategory)); }while( result.categoryId + '.' + result.productId in used || ( ( cur.c > 1 && result.categoryId === last.c ) && !useCategory ) );
if(error) if( error )
break; break;
if(tries !== 100){ if( tries !== 100 ){
cur.c = result.categoryId; cur.c = result.categoryId;
used[result.categoryId+'.'+result.productId] = true; used[ result.categoryId + '.' + result.productId ] = true;
list.push(result); list.push( result );
if(args.nolog) if( args.nolog )
delete result.log; delete result.log;
last = cur; last = cur;
generated++; generated++;
result.number = generated; result.number = generated;
} }
} }
}
if(args.stats){ if(args.stats){
res.header("Content-Type", "text/html; charset=utf-8"); res.header("Content-Type", "text/html; charset=utf-8");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment