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

new tag based sequence generator

parent 2ea85a78
Pipeline #566 canceled with stage
......@@ -77,14 +77,14 @@ quizTypes.checkbox = [ ComponentsOfProduct = {
type: 'Select components of product',
products: {from: 2, to: 4},
minSimilarTags: 1,
from(log){
from(log, isTest){
const products = qB.randomProduct({
minComponents: 1,
amount: this.products,
connectedByTags: true,
minSimilarTags: this.minSimilarTags,
doNotTrim: true
}, log),
}, log, isTest),
componentsWithProducts = qB.getComponentsWithSharedProducts(products, 2);
......
......@@ -7,7 +7,7 @@ const qB = {
minSimilarTags,
doNotTrim,
withPhoto
}, log) {
}, log, isTest) {
let filtered = Object.values(dP.products);
......@@ -74,7 +74,7 @@ const qB = {
similarClusters = Object.keys(similarTags)
.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);
......@@ -89,7 +89,7 @@ const qB = {
let result;
if(single){
result = rand(filtered.filter(p=>p.use));
result = rand(filtered.filter(isTest));
log.push('Matched product: '+result.title);
}else{
......@@ -98,7 +98,7 @@ const qB = {
return false;
}
let used = rand(filtered.filter(p=>p.use)),
let used = rand(filtered.filter(isTest)),
other = filtered.slice();
other.splice(other.indexOf(used));
......@@ -192,11 +192,11 @@ const qB = {
prebuild: {
similarTaggedProductWithPhotoAndComponents: {
fn: function(log) {
fn: function(log, isTest) {
this.getEmAll = true;
this.products = {min: 2};
const possibilities = shuffle(
qB.prebuild.similarTaggedProductWithPhoto.fn.call(this, log)
qB.prebuild.similarTaggedProductWithPhoto.fn.call(this, log, isTest)
);
if(!possibilities || !possibilities.length)
return false;
......@@ -238,7 +238,7 @@ const qB = {
questionMinComponentsCount: Number,
products: {min: Number, max: Number},
fn: function(log) {
fn: function(log, isTest) {
let allProducts = Object.values(dP.products);
let products = Object.values(dP.products)
.filter(p=>p.image)
......@@ -251,7 +251,7 @@ const qB = {
}
const possibility = products.map(p=>{
if(!p.use)
if(!isTest(p))
return false;
const similar = qB.getProductsWithTags({
products: allProducts,
......@@ -285,20 +285,20 @@ const qB = {
// product similarity (minimal matched tags count)
minSimilarTags: 2,
fn(log){
fn(log, isTest){
let products = qB.randomProduct({
minComponents: this.questionCmpAmount,
amount: this.products,
connectedByTags: true,
minSimilarTags: this.minSimilarTags
}, log);
}, log, isTest);
for( let i = 0, _i = products.length; i < _i; i++ ){
const product = products[ i ];
/* if(this.withPhoto && !product.image)
continue;*/
if(!product.use)
if(!isTest(product))
continue;
const cmps = product
.getComponents()
......
......@@ -88,8 +88,8 @@ quizTypes.radio = [
question: ({product})=> `Выберите лишний ингредиент, НЕ входящий в продукт "${product.title}"`,
type: 'Do not contain',
from: (log)=>{
const product = qB.randomProduct({ minComponents: 2, single: true }, log);
from: (log, isTest)=>{
const product = qB.randomProduct({ minComponents: 2, single: true }, log, isTest);
return {baseProduct: product, product}
},
......
......@@ -81,10 +81,10 @@ const standardGenerator = function(random) {
log
};
};
const quizGenerator = function(type, photo, subType) {
return _quizGenerator(type, photo, subType);
const quizGenerator = function(type, photo, isTest, subType) {
return _quizGenerator(type, photo, isTest, subType);
},
_quizGenerator = function(type, photo, subType, attempt) {
_quizGenerator = function(type, photo, isTest, subType, attempt) {
attempt = attempt || 0;
const initialSeed = Math.random.seeded.getStringSeed();
let log;
......@@ -119,23 +119,23 @@ const quizGenerator = function(type, photo, subType) {
if(!cfg)
return {answers: [], log};
const source = cfg.from.call(cfg, log);
const source = cfg.from.call(cfg, log, isTest);
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){
// давай по новой
return _quizGenerator( type, photo, subType, attempt + 1 );
return _quizGenerator( type, photo, isTest, subType, attempt + 1 );
}
return {
seed: initialSeed,
type,
categoryId: 1,
productId: source.baseProduct.id,
question: cfg.question.call(cfg, source, log),
question: cfg.question.call(cfg, source, log, isTest),
answers,
image: !photo?null:source.baseProduct.image,
......
......@@ -32,7 +32,7 @@ var js = sources.map(a=>path.join(__dirname,'../../public',a)).map(n=>fs.readFil
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);
......@@ -350,10 +350,12 @@ module.exports = {
connections: [],
products: {},
components: {},
standardQuestions: {}
standardQuestions: {},
category: {}
};
var cmpID = 1;
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 ){
category.cards.forEach( card => {
card.components.forEach( cmp => {
......@@ -557,7 +559,8 @@ module.exports = {
nolog: {required: false, description: 'remove human readable log', type: Boolean},
stats: {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) {
await ctx.ready; const rand = ctx.rand, seeded = ctx.seeded;
......@@ -581,97 +584,197 @@ module.exports = {
globalMaxTries = args.count*500,
globalTries = 0;
if(args.count>100)args.count = 100;
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 ),
photo = option.photo; //seeded() > 0.7
let cur = {
m: multiple,//seeded() > 24 / ( 46 + 24 ),
p: photo,//seeded() > 0.7,
c: 1
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];
};
if( (useCategory && !categoryHash[1]) || seeded() > 0.3 ){
//if( last.c === 1 && seeded() > 0.5 ){
cur = {
c: 2
};
let id, tries = 0;
/*do{
id = rand(1, countStdQ);
tries++;
if(tries === 100){
break;
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);
}
}while('2.'+id in used);*/
if(tries===100)
continue;
cur.id = id;
}
}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 tries = 0,
result;
do{
if(cur.c === 1){
result = ctx.quizGenerator( cur.m ? 'checkbox' : 'radio', cur.p );
// СФОРМИРОВАЛИ ОЖИДАНИЮ
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 counter = 0, r = 2;
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(useCategory){
if(result){
list.push( result );
generated++;
}
step = groups.length>1?groups.slice().filter(i=>i!== take): groups.slice();
}
let possible = Object.values(data.standardQuestions)
.map( ( q ) => ( { q, c: q.category_id, n: q.qID } ) )
.filter( q => q.c in categoryHash )
.filter( q => !(q.c +'.'+q.n in used) )
.map( q => q.n );
if(possible.length){
r = rand( possible )
}else{
error = 'Not enough questions';
// 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 ),
photo = option.photo; //seeded() > 0.7
let cur = {
m: multiple,//seeded() > 24 / ( 46 + 24 ),
p: photo,//seeded() > 0.7,
c: 1
};
if( ( useCategory && !categoryHash[ 1 ] ) || seeded() > 0.3 ){
//if( last.c === 1 && seeded() > 0.5 ){
cur = {
c: 2
};
let id, tries = 0;
/*do{
id = rand(1, countStdQ);
tries++;
if(tries === 100){
break;
}
}while('2.'+id in used);*/
if( tries === 100 )
continue;
cur.id = id;
}
let tries = 0,
result;
do{
if( cur.c === 1 ){
result = ctx.quizGenerator( cur.m ? 'checkbox' : 'radio', cur.p );
}else{
do{
r = rand( 1, countStdQ );
let q = data.standardQuestions[ r ];
if( q && !( q.category_id + '.' + q.qID in used ) )
let counter = 0, r = 2;
if( useCategory ){
let possible = Object.values( data.standardQuestions )
.map( ( q ) => ( { q, c: q.category_id, n: q.qID } ) )
.filter( q => q.c in categoryHash )
.filter( q => !( q.c + '.' + q.n in used ) )
.map( q => q.n );
if( possible.length ){
r = rand( possible )
}else{
error = 'Not enough questions';
break;
counter++;
}while( counter < 100 );
}
}
result = ctx.standardGenerator(()=>r);
}else{
do{
r = rand( 1, countStdQ );
let q = data.standardQuestions[ r ];
if( q && !( q.category_id + '.' + q.qID in used ) )
break;
counter++;
}while( counter < 100 );
}
result = ctx.standardGenerator( () => r );
if(!result){
tries = 100;
break;
if( !result ){
tries = 100;
break;
}
}
}
tries++;
if(tries === 100)
tries++;
if( tries === 100 )
break;
}while( result.categoryId + '.' + result.productId in used || ( ( cur.c > 1 && result.categoryId === last.c ) && !useCategory ) );
if( error )
break;
}while(result.categoryId+'.'+result.productId in used || ((cur.c>1 && result.categoryId === last.c)&& !useCategory));
if(error)
break;
if(tries !== 100){
cur.c = result.categoryId;
used[result.categoryId+'.'+result.productId] = true;
list.push(result);
if(args.nolog)
delete result.log;
last = cur;
generated++;
result.number = generated;
if( tries !== 100 ){
cur.c = result.categoryId;
used[ result.categoryId + '.' + result.productId ] = true;
list.push( result );
if( args.nolog )
delete result.log;
last = cur;
generated++;
result.number = generated;
}
}
}
if(args.stats){
res.header("Content-Type", "text/html; charset=utf-8");
let stats = {};
......
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