- Added bestiaryRoutes.js to handle API endpoints for fetching enemies, full bestiary data, and statistics. - Implemented caching mechanism for bestiary data with a 5-minute expiration. - Created transformation functions for bestiary entries to standardize data format for the dice roller. - Added admin-only endpoint to force reload bestiary data with GM authentication. chore: Create scripts for analyzing and cleaning bestiary data - Developed analyze-bestiary-quality.js to review data quality and identify duplicates or inconsistencies. - Implemented clean-bestiary-fields.js to extract and clean specific fields from bestiary entries, reducing text duplication. - Created clean-corrupted-bestiary.js to remove corrupted entries based on defined patterns. - Generated a comprehensive database-cleanup-report.md summarizing the cleaning process and results. build: Add fast build script for streamlined deployment - Introduced fast-build.sh to facilitate quick builds and PM2 reloads without reinstalling dependencies. test: Add GM authentication logic test - Created test-gm-auth.js to verify the correctness of GM authentication logic with various test cases.
104 lines
3.5 KiB
JavaScript
Executable File
104 lines
3.5 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const BESTIARY_PATH = path.join(__dirname, '../database/deathwatch-bestiary-extracted.json');
|
|
|
|
function analyzeDatabase() {
|
|
const data = JSON.parse(fs.readFileSync(BESTIARY_PATH, 'utf8'));
|
|
const entries = data.results;
|
|
|
|
console.log('=== DATABASE QUALITY REVIEW ===');
|
|
console.log(`Total entries: ${entries.length}`);
|
|
console.log(`Generated at: ${data.generatedAt}`);
|
|
console.log();
|
|
|
|
// Check for duplicate stats
|
|
console.log('=== CHECKING FOR DUPLICATE STATS ===');
|
|
const statGroups = new Map();
|
|
entries.forEach((entry, i) => {
|
|
const stats = entry.stats?.profile || entry.profile;
|
|
if (stats) {
|
|
const key = `T:${stats.t}_W:${entry.wounds}_WS:${stats.ws}`;
|
|
if (!statGroups.has(key)) {
|
|
statGroups.set(key, []);
|
|
}
|
|
statGroups.get(key).push({ index: i, name: entry.bestiaryName || entry.name });
|
|
}
|
|
});
|
|
|
|
let duplicateGroups = 0;
|
|
statGroups.forEach((group, key) => {
|
|
if (group.length > 1) {
|
|
duplicateGroups++;
|
|
console.log(`❌ Duplicate stats (${key}):`);
|
|
group.forEach(item => console.log(` - ${item.name}`));
|
|
console.log();
|
|
}
|
|
});
|
|
|
|
// Check for specific data quality issues
|
|
console.log('=== DATA QUALITY ISSUES ===');
|
|
let issueCount = 0;
|
|
|
|
entries.forEach((entry, i) => {
|
|
const name = entry.bestiaryName || entry.name || 'Unknown';
|
|
const stats = entry.stats?.profile || entry.profile || {};
|
|
const wounds = entry.wounds || entry.stats?.wounds;
|
|
const issues = [];
|
|
|
|
// Check T:78 entries (suspicious Hive Tyrant stats)
|
|
if (stats.t === 78 && wounds === 120) {
|
|
if (!name.toLowerCase().includes('tyrant')) {
|
|
issues.push('Has Hive Tyrant stats but is not a Hive Tyrant');
|
|
}
|
|
}
|
|
|
|
// Check for unrealistic civilian stats
|
|
if (name.toLowerCase().includes('civilian') && (stats.t > 40 || wounds > 20)) {
|
|
issues.push('Civilian with combat-level stats');
|
|
}
|
|
|
|
// Check for unrealistic servitor stats
|
|
if (name.toLowerCase().includes('servitor') && stats.t > 50) {
|
|
issues.push('Servitor with unrealistic toughness');
|
|
}
|
|
|
|
// Check for missing core data
|
|
if (!stats.t || !wounds) {
|
|
issues.push('Missing essential stats (Toughness or Wounds)');
|
|
}
|
|
|
|
// Check page text consistency
|
|
if (entry.pageText && entry.pageText.includes('Tyranid') && !name.toLowerCase().includes('tyranid')) {
|
|
issues.push('Page text mentions Tyranids but creature name suggests otherwise');
|
|
}
|
|
|
|
if (issues.length > 0) {
|
|
issueCount++;
|
|
console.log(`❌ ${i+1}. ${name}`);
|
|
console.log(` Book: ${entry.book || 'Unknown'} Page: ${entry.page || 'Unknown'}`);
|
|
console.log(` Stats: T:${stats.t} W:${wounds} WS:${stats.ws} BS:${stats.bs}`);
|
|
issues.forEach(issue => console.log(` ⚠️ ${issue}`));
|
|
console.log();
|
|
}
|
|
});
|
|
|
|
// Summary
|
|
console.log('=== SUMMARY ===');
|
|
console.log(`✅ Clean entries: ${entries.length - issueCount}`);
|
|
console.log(`❌ Problematic entries: ${issueCount}`);
|
|
console.log(`📊 Duplicate stat groups: ${duplicateGroups}`);
|
|
|
|
if (issueCount > 0) {
|
|
console.log();
|
|
console.log('🔧 RECOMMENDATIONS:');
|
|
console.log('- Review entries with identical stats for data corruption');
|
|
console.log('- Check page text vs creature names for mismatched data');
|
|
console.log('- Verify civilian/servitor entries have appropriate stats');
|
|
console.log('- Consider re-extracting problematic entries from source PDFs');
|
|
}
|
|
}
|
|
|
|
analyzeDatabase();
|