158 lines
5.6 KiB
TypeScript
158 lines
5.6 KiB
TypeScript
|
|
import PocketBase from 'pocketbase';
|
|||
|
|
|
|||
|
|
const PB_URL = 'http://127.0.0.1:8090';
|
|||
|
|
const pb = new PocketBase(PB_URL);
|
|||
|
|
|
|||
|
|
interface Post {
|
|||
|
|
id: string;
|
|||
|
|
title: string;
|
|||
|
|
content: string;
|
|||
|
|
slug: string;
|
|||
|
|
category: string;
|
|||
|
|
draft: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
interface AnalysisResult {
|
|||
|
|
title: string;
|
|||
|
|
slug: string;
|
|||
|
|
category: string;
|
|||
|
|
hasClaim: boolean;
|
|||
|
|
hasTakeaway: boolean;
|
|||
|
|
hasPronouns: boolean;
|
|||
|
|
hasProof: boolean;
|
|||
|
|
h2Issues: string[];
|
|||
|
|
problems: string[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function analyzePost(post: Post): AnalysisResult {
|
|||
|
|
const result: AnalysisResult = {
|
|||
|
|
title: post.title,
|
|||
|
|
slug: post.slug,
|
|||
|
|
category: post.category || 'unknown',
|
|||
|
|
hasClaim: false,
|
|||
|
|
hasTakeaway: false,
|
|||
|
|
hasPronouns: false,
|
|||
|
|
hasProof: false,
|
|||
|
|
h2Issues: [],
|
|||
|
|
problems: []
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const content = post.content || '';
|
|||
|
|
|
|||
|
|
// Check for Claim patterns directly in content (including HTML)
|
|||
|
|
// Look for new first paragraph that starts with Claim-like content
|
|||
|
|
// Check first 1500 chars to catch posts where Claim may have been inserted
|
|||
|
|
const claimPatterns = [
|
|||
|
|
{ pattern: /За\s+\d+/i, name: 'За год' },
|
|||
|
|
{ pattern: /В\s+\d+\s+(год|месяц)/i, name: 'В год/месяц' },
|
|||
|
|
{ pattern: /\d+%/i, name: 'Процент' },
|
|||
|
|
{ pattern: /Вы можете/i, name: 'Вы можете' },
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const prefix = content.substring(0, 3000);
|
|||
|
|
result.hasClaim = claimPatterns.some(p => p.pattern.test(prefix));
|
|||
|
|
|
|||
|
|
// Proof: contains numbers
|
|||
|
|
const numberPatterns = [/\d+%/g, /\d+\s+(лет|месяцев|дней|тысяч|рублей)/gi];
|
|||
|
|
result.hasProof = numberPatterns.some(p => p.test(content));
|
|||
|
|
|
|||
|
|
// Pronouns check
|
|||
|
|
const pronouns = /\b(мы|наш|наша|наши|их|он|она|оно|этот|эта|эти)\b/gi;
|
|||
|
|
result.hasPronouns = pronouns.test(content);
|
|||
|
|
|
|||
|
|
// Takeaway check
|
|||
|
|
const takeawayPatterns = [
|
|||
|
|
/что\s+делать/i,
|
|||
|
|
/обращайтесь/i,
|
|||
|
|
/позвоните/i,
|
|||
|
|
/закажите/i,
|
|||
|
|
/получите/i,
|
|||
|
|
/напишите/i,
|
|||
|
|
/свяжитесь/i
|
|||
|
|
];
|
|||
|
|
result.hasTakeaway = takeawayPatterns.some(p => p.test(content));
|
|||
|
|
|
|||
|
|
// H2 analysis - check all H2s in content
|
|||
|
|
const h2Matches = content.match(/<h2[^>]*>[^<]+<\/h2>/gi) || [];
|
|||
|
|
const badH2s = ['проблема', 'подход', 'ситуация', 'что было', 'решение', 'как помочь', 'поможет'];
|
|||
|
|
h2Matches.forEach(h2 => {
|
|||
|
|
const h2Text = h2.toLowerCase();
|
|||
|
|
if (badH2s.some(b => h2Text.includes(b))) {
|
|||
|
|
result.h2Issues.push(h2.replace(/<[^>]+>/g, ''));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Collect problems
|
|||
|
|
if (!result.hasClaim) result.problems.push('Нет Claim в первом абзаце');
|
|||
|
|
if (!result.hasTakeaway) result.problems.push('Нет Takeaway');
|
|||
|
|
if (result.hasPronouns) result.problems.push('Есть местоимения');
|
|||
|
|
if (!result.hasProof) result.problems.push('Нет конкретных цифр');
|
|||
|
|
if (result.h2Issues.length > 0) result.problems.push(`Размытые H2: ${result.h2Issues.join(', ')}`);
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function main() {
|
|||
|
|
console.log('📊 Анализ постов блога по методологии Answer Unit\n');
|
|||
|
|
console.log('='.repeat(60));
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const postsResult = await pb.collection('posts').getList(1, 500);
|
|||
|
|
const posts = postsResult.items as unknown as Post[];
|
|||
|
|
|
|||
|
|
console.log(`Найдено постов: ${posts.length}\n`);
|
|||
|
|
|
|||
|
|
// Filter for non-draft posts
|
|||
|
|
const activePosts = posts.filter(p => !p.draft);
|
|||
|
|
console.log(`Опубликованных постов: ${activePosts.length}\n`);
|
|||
|
|
|
|||
|
|
const results = activePosts.map(analyzePost);
|
|||
|
|
|
|||
|
|
// Stats
|
|||
|
|
const withClaim = results.filter(r => r.hasClaim).length;
|
|||
|
|
const withTakeaway = results.filter(r => r.hasTakeaway).length;
|
|||
|
|
const withPronouns = results.filter(r => r.hasPronouns).length;
|
|||
|
|
const withProof = results.filter(r => r.hasProof).length;
|
|||
|
|
const withH2Issues = results.filter(r => r.h2Issues.length > 0).length;
|
|||
|
|
const problemCount = results.filter(r => r.problems.length > 0).length;
|
|||
|
|
|
|||
|
|
const n = activePosts.length;
|
|||
|
|
console.log('📈 ОБЩАЯ СТАТИСТИКА:');
|
|||
|
|
console.log('-'.repeat(40));
|
|||
|
|
console.log(` ✓ Есть Claim: ${withClaim}/${n} (${n > 0 ? Math.round(withClaim/n*100) : 0}%)`);
|
|||
|
|
console.log(` ✓ Есть Takeaway: ${withTakeaway}/${n} (${n > 0 ? Math.round(withTakeaway/n*100) : 0}%)`);
|
|||
|
|
console.log(` ✗ Есть местоимения: ${withPronouns}/${n} (${n > 0 ? Math.round(withPronouns/n*100) : 0}%)`);
|
|||
|
|
console.log(` ✓ Есть Proof (цифры): ${withProof}/${n} (${n > 0 ? Math.round(withProof/n*100) : 0}%)`);
|
|||
|
|
console.log(` ✗ Проблемы с H2: ${withH2Issues}/${n}`);
|
|||
|
|
console.log(` ⚠️ Постов с проблемами: ${problemCount}/${n}\n`);
|
|||
|
|
|
|||
|
|
// Problem posts
|
|||
|
|
const problemPosts = results.filter(r => r.problems.length > 0);
|
|||
|
|
|
|||
|
|
if (problemPosts.length > 0) {
|
|||
|
|
console.log('❌ ПОСТЫ С ПРОБЛЕМАМИ:');
|
|||
|
|
console.log('-'.repeat(40));
|
|||
|
|
problemPosts.forEach((r, i) => {
|
|||
|
|
console.log(`\n${i+1}. ${r.title}`);
|
|||
|
|
console.log(` slug: ${r.slug}`);
|
|||
|
|
console.log(` category: ${r.category}`);
|
|||
|
|
r.problems.forEach(p => console.log(` - ${p}`));
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Good posts
|
|||
|
|
const goodPosts = results.filter(r => r.problems.length === 0);
|
|||
|
|
if (goodPosts.length > 0) {
|
|||
|
|
console.log('\n\n✅ ПОСТЫ БЕЗ ПРОБЛЕМ:');
|
|||
|
|
console.log('-'.repeat(40));
|
|||
|
|
goodPosts.forEach(r => {
|
|||
|
|
console.log(` • ${r.title}`);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Ошибка:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main();
|