openvibers/openvibe.meme Commits

Classics publish on their score, text and deny list; a re-gate with no AI (#34)

OP OpenVibers committed 12 days ago · 2a950115d1 · 3 files, +78 −4

Changes

ddiff --git a/scripts/regate-classics.js b/scripts/regate-classics.js
nnew file mode 100644
iindex 0000000..519f103
---- /dev/null
++++ b/scripts/regate-classics.js
@@@ -0,0 +1,54 @@
++#!/usr/bin/env node
++'use strict';
++
++/**
++ * Re-gate the hidden classics with today's rule (server/ai/gate.js evaluateClassic), from the judgement each one already
++ * has: no AI is called again. A classic that clears the rule now is published through the store (the origin check and
++ * the Search document come with it) and the decision is a status event; the rest stay hidden with their reasons.
++ *
++ * node scripts/regate-classics.js --dry-run # report only
++ * node scripts/regate-classics.js
++ */
++process.env.NODE_ENV = process.env.NODE_ENV || 'development';
++require('dotenv').config();
++
++const { load } = require('../server/config');
++const { openStore } = require('../server/db');
++const { createStore } = require('../server/store');
++const { createSearchIndex } = require('../server/search');
++const { evaluateClassic } = require('../server/ai/gate');
++
++async function regate({ s, entries, dryRun = false, minScore = 0.75 }) {
++ const rows = await s.db.many(`SELECT e.slug, e.title, j.score, j.title AS j_title, j.summary, j.origin, j.spread, j.examples, j.denied, j.looks, j.reason
++ FROM meme_entries e JOIN meme_judgements j ON j.entry_id = e.id
++ WHERE e.kind = 'classic' AND e.visibility <> 'public' ORDER BY e.slug`);
++ const out = { checked: rows.length, published: [], kept: 0, refused: [] };
++ for (const r of rows) {
++ const judgement = { score: r.score, title: r.j_title, summary: r.summary, origin: r.origin, spread: r.spread, examples: r.examples, denied: r.denied, looks: r.looks };
++ const decision = evaluateClassic({ judgement, minScore, entry: { title: r.title } });
++ if (!decision.publish) { out.kept++; continue; }
++ if (dryRun) { out.published.push(r.slug); continue; }
++ const done = await entries.publish(r.slug, { changedBy: 'service:meme:regate' });
++ if (!done.ok) { out.refused.push(`${r.slug}: ${done.code || done.message || 'refused'}`); continue; }
++ await entries.logDecision({
++ entryId: done.entry.id, decision: 'publish', fromStatus: 'classic', toStatus: 'classic', fromVisibility: 'hidden', toVisibility: 'public',
++ score: decision.score, reason: `re-gated on 10-31's classic rule: the AI scored it ${Number(decision.score).toFixed(2)}`, changedBy: 'service:meme:regate',
++ });
++ out.published.push(r.slug);
++ }
++ return out;
++}
++
++module.exports = { regate };
++
++if (require.main === module) {
++ (async () => {
++ const config = load();
++ const s = await openStore(config);
++ const entries = createStore(s, { search: createSearchIndex({ config, s }) });
++ const out = await regate({ s, entries, dryRun: process.argv.includes('--dry-run') });
++ console.log(`[regate-classics] checked ${out.checked}, published ${out.published.length}, kept ${out.kept}, refused ${out.refused.length}`);
++ if (out.refused.length) console.log(out.refused.slice(0, 10).join('\n'));
++ await s.close();
++ })().catch((err) => { console.error(err); process.exit(1); });
++}
ddiff --git a/server/ai/gate.js b/server/ai/gate.js
iindex 47da202..5a0cf8b 100644
---- a/server/ai/gate.js
++++ b/server/ai/gate.js
@@@ -67,7 +67,10 @@ function evaluate({ entry = null, citations = [], judgement = null, gate = {} }
/**
* The encyclopedia's rule. A classic has no uses to count: it is published when the AI scored it as a real, widely
-- * used meme and its own text is clean (no looks, no denied word, nothing denied in what it cites).
++ * used meme, it is written (a title, a summary, where it started, how it spread) and nothing it cites is denied.
++ * Two bars of the candidates' rule do not apply (10-31): two example uses is how a chat catchphrase proves itself, not
++ * a meme Wikipedia already documents; and the owner lifted the looks rule that day (AI may describe people; only
++ * explicit content stays gated, which the deny list keeps).
*/
function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {}) {
const reasons = [];
@@@ -77,14 +80,11 @@ function evaluateClassic({ judgement = null, minScore = 0.75, entry = null } = {
if (!Number.isFinite(score)) reasons.push('no score');
else if (score < minScore) reasons.push(`scored ${score.toFixed(2)}, under ${minScore.toFixed(2)}`);
if (judgement.denied) reasons.push('a denied source');
-- if (judgement.looks) reasons.push('the text names looks');
if (!judgement.title) reasons.push('no title');
if (!judgement.summary) reasons.push('no summary');
if (!judgement.origin) reasons.push('no origin');
if (!judgement.spread) reasons.push('no spread');
-- if (!Array.isArray(judgement.examples) || judgement.examples.length < 2) reasons.push('fewer than two examples');
}
-- if (entry && entry.title && hasLooks(entry.title)) reasons.push('the title names looks');
return {
publish: reasons.length === 0,
reasons,
ddiff --git a/test/classic-gate.test.js b/test/classic-gate.test.js
nnew file mode 100644
iindex 0000000..696ad0d
---- /dev/null
++++ b/test/classic-gate.test.js
@@@ -0,0 +1,20 @@
++'use strict';
++// The classics' rule (10-31): a classic Wikipedia documents is published on its score, its written text and a clean
++// deny list. It needs no example uses (that is how a chat catchphrase proves itself) and is not held for describing
++// people (the owner lifted the looks rule that day). A low score, a denied source or missing text still keep it hidden.
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const { evaluateClassic } = require('../server/ai/gate');
++
++const ok = { score: 0.9, title: 'Flex Tape', summary: 'An infomercial that became a meme.', origin: '2017 infomercial', spread: 'Reaction videos', examples: [], denied: false, looks: false };
++assert.strictEqual(evaluateClassic({ judgement: ok }).publish, true, 'no examples needed for a classic');
++assert.strictEqual(evaluateClassic({ judgement: { ...ok, looks: true }, entry: { title: 'The man in the hat' } }).publish, true, 'describing people no longer holds a classic back');
++assert.strictEqual(evaluateClassic({ judgement: { ...ok, score: 0.5 } }).publish, false, 'a low score still does');
++assert.strictEqual(evaluateClassic({ judgement: { ...ok, denied: true } }).publish, false, 'a denied source still does');
++assert.strictEqual(evaluateClassic({ judgement: { ...ok, summary: '' } }).publish, false, 'missing text still does');
++
++const script = fs.readFileSync(path.join(__dirname, '..', 'scripts/regate-classics.js'), 'utf8');
++assert.ok(!/createMemeAi|judge\.judge\(/.test(script), 'the re-gate calls no AI');
++assert.match(script, /entries\.publish\(r\.slug/, 'and publishes through the store (origin check, Search document)');
++console.log('classic gate: score, text and deny list; no examples or looks bar');