Refactor code structure for improved readability and maintainability
This commit is contained in:
100
scripts/ocr-and-rebuild-rules.js
Normal file
100
scripts/ocr-and-rebuild-rules.js
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env node
|
||||
// OCR + rebuild rules from an image using tesseract.js
|
||||
// Usage: node scripts/ocr-and-rebuild-rules.js /path/to/image.png
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { createWorker } = require('tesseract.js');
|
||||
const Database = require('better-sqlite3');
|
||||
|
||||
async function main(){
|
||||
const img = process.argv[2];
|
||||
if(!img){
|
||||
console.error('Usage: node scripts/ocr-and-rebuild-rules.js /path/to/image.png');
|
||||
process.exit(2);
|
||||
}
|
||||
if(!fs.existsSync(img)){
|
||||
console.error('Image not found:', img);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
const dbPath = path.join(repoRoot, 'database', 'sqlite', 'deathwatch.db');
|
||||
const backupsDir = path.join(repoRoot, 'database', 'backups');
|
||||
if(!fs.existsSync(dbPath)){
|
||||
console.error('DB not found at', dbPath); process.exit(1);
|
||||
}
|
||||
if(!fs.existsSync(backupsDir)) fs.mkdirSync(backupsDir,{recursive:true});
|
||||
const nowTs = ()=> new Date().toISOString().replace(/[:.]/g,'-');
|
||||
|
||||
console.log('Running OCR on', img);
|
||||
const worker = createWorker({
|
||||
logger: m => { if(m && m.status) process.stdout.write(`\rOCR: ${m.status} ${(m.progress||0).toFixed(2)}`); }
|
||||
});
|
||||
await worker.load();
|
||||
await worker.loadLanguage('eng');
|
||||
await worker.initialize('eng');
|
||||
const { data: { text } } = await worker.recognize(img);
|
||||
await worker.terminate();
|
||||
console.log('\nOCR complete,', text.length, 'chars');
|
||||
|
||||
// Split text into lines and use heading heuristics: a heading is a short line (<=60 chars) that is mostly letters, possibly contains parentheses or ALL CAPS.
|
||||
const lines = text.split(/\r?\n/).map(l=>l.replace(/\u00A0/g,' ').trim());
|
||||
const isHeading = (l)=>{
|
||||
if(!l) return false;
|
||||
if(l.length>60) return false;
|
||||
if(/\d{1,3}\s?[-–—]/.test(l)) return false; // avoid numbered table rows
|
||||
if(/Table\s+\d+/i.test(l)) return false;
|
||||
if(/^[A-Z0-9][A-Z0-9 \-'()]+$/.test(l) && l.split(' ').length<=6) return true; // ALL CAPS
|
||||
if(/\w+\s*\(.+\)/.test(l)) return true; // has parentheses like (Basic)
|
||||
if(/^([A-Za-z]{3,}\s?){1,3}$/.test(l)) return true; // 1-3 word simple title
|
||||
return false;
|
||||
};
|
||||
|
||||
const blocks = [];
|
||||
let curr = null;
|
||||
for(const line of lines){
|
||||
if(isHeading(line)){
|
||||
if(curr) blocks.push(curr);
|
||||
curr = {title: line, content: ''};
|
||||
}else{
|
||||
if(!curr) continue; // skip until first heading
|
||||
if(curr.content.length) curr.content += '\n';
|
||||
curr.content += line;
|
||||
}
|
||||
}
|
||||
if(curr) blocks.push(curr);
|
||||
console.log('Detected', blocks.length, 'blocks from OCR');
|
||||
|
||||
// Simple filter: content length > 30
|
||||
const skills = blocks.filter(b=> (b.content||'').replace(/\s+/g,'').length>30);
|
||||
console.log('Filtered to', skills.length, 'skill-like blocks');
|
||||
|
||||
const db = new Database(dbPath);
|
||||
const backupFile = path.join(backupsDir, `rules-backup-before-ocr-rebuild-${nowTs()}.json`);
|
||||
const rows = db.prepare('SELECT * FROM rules').all();
|
||||
fs.writeFileSync(backupFile, JSON.stringify(rows,null,2));
|
||||
console.log('Wrote DB backup to', backupFile);
|
||||
|
||||
const deleteInfo = db.prepare('DELETE FROM rules').run();
|
||||
console.log('Purged rules table, deleted rows:', deleteInfo.changes);
|
||||
|
||||
const insert = db.prepare(`INSERT INTO rules (rule_id,title,content,page,source,source_abbr,category,created_at) VALUES (?,?,?,?,?,?,?,datetime('now'))`);
|
||||
let i=1;
|
||||
const txn = db.transaction((rows)=>{
|
||||
for(const r of rows){
|
||||
const ruleId = `ocr_skill_${i}_${r.title.replace(/[^a-z0-9]+/ig,'_').toLowerCase()}`;
|
||||
insert.run(ruleId, r.title, r.content, null, 'Core Rulebook', 'CR', 'skills');
|
||||
i++;
|
||||
}
|
||||
});
|
||||
txn(skills);
|
||||
console.log('Inserted', skills.length, 'skill rows');
|
||||
const report = path.join(backupsDir, `rules-ocr-rebuild-report-${nowTs()}.json`);
|
||||
fs.writeFileSync(report, JSON.stringify({inserted: skills.length, sample: skills.slice(0,50)}, null, 2));
|
||||
console.log('Wrote report to', report);
|
||||
db.close();
|
||||
console.log('Done');
|
||||
}
|
||||
|
||||
main().catch(e=>{ console.error(e); process.exit(1); });
|
||||
Reference in New Issue
Block a user