/ scripts / expand-low-keyword-countries.js
expand-low-keyword-countries.js
  1  #!/usr/bin/env node
  2  
  3  /**
  4   * Expand keywords for countries with <50 final keywords
  5   *
  6   * Target: Get 150-200 raw keywords per country to achieve 50+ after filtering
  7   *
  8   * Countries to expand:
  9   * - IN (India): 17 final
 10   * - NO (Norway): 30 final
 11   * - SG (Singapore): 30 final
 12   * - CH (Switzerland): 42 final
 13   * - NL (Netherlands): 43 final
 14   * - ID (Indonesia): 44 final
 15   * - DK (Denmark): 45 final
 16   * - CN (China): 48 final
 17   * - FR (France): 49 final
 18   */
 19  
 20  import 'dotenv/config';
 21  import { generateSearchVolumeCSVOptimized } from '../src/utils/keyword-validator-optimized.js';
 22  import { filterKeywordsPreOverview } from '../src/utils/keyword-filter.js';
 23  import { stringify } from 'csv-stringify/sync';
 24  import { parse } from 'csv-parse/sync';
 25  import fs from 'fs';
 26  import Logger from '../src/utils/logger.js';
 27  
 28  const logger = new Logger('ExpandKeywords');
 29  
 30  // Countries that need expansion (less than 50 final keywords)
 31  const COUNTRIES_TO_EXPAND = [
 32    { code: 'IN', name: 'India', current: 17, target: 150 },
 33    { code: 'NO', name: 'Norway', current: 30, target: 100 },
 34    { code: 'SG', name: 'Singapore', current: 30, target: 100 },
 35    { code: 'CH', name: 'Switzerland', current: 42, target: 120 },
 36    { code: 'NL', name: 'Netherlands', current: 43, target: 120 },
 37    { code: 'ID', name: 'Indonesia', current: 44, target: 150 },
 38    { code: 'DK', name: 'Denmark', current: 45, target: 100 },
 39    { code: 'CN', name: 'China', current: 48, target: 80 },
 40    { code: 'FR', name: 'France', current: 49, target: 150 },
 41  ];
 42  
 43  // Core seed keywords to expand from
 44  const CORE_SEEDS = [
 45    'plumber',
 46    'electrician',
 47    'carpenter',
 48    'painter',
 49    'pest control',
 50    'locksmith',
 51    'handyman',
 52    'landscaper',
 53    'lawn care',
 54    'tree service',
 55    'HVAC repair',
 56    'heating repair',
 57    'air conditioning repair',
 58    'garage door repair',
 59    'appliance repair',
 60    'roofer',
 61    'gutter cleaning',
 62    'pressure washing',
 63    'junk removal',
 64    'concrete contractor',
 65    'driveway paving',
 66    'fencing',
 67    'deck builder',
 68    'bathroom remodeling',
 69    'kitchen remodeling',
 70    'flooring contractor',
 71    'waterproofing',
 72    'insulation contractor',
 73    'siding contractor',
 74    'drywall contractor',
 75    'masonry contractor',
 76    'pool service',
 77    'pool cleaning',
 78    'solar panel installation',
 79    'chimney sweep',
 80  ];
 81  
 82  /**
 83   * Expand keywords for a single country
 84   */
 85  async function expandCountry(country) {
 86    const countryCode = country.code;
 87    const countryDir = `./data/${countryCode.toLowerCase()}`;
 88  
 89    logger.info(`\n${'='.repeat(60)}`);
 90    logger.info(
 91      `${country.name} (${countryCode}) - Current: ${country.current}, Target: ${country.target}`
 92    );
 93    logger.info('='.repeat(60));
 94  
 95    // Create seed file
 96    const seedFile = `${countryDir}/businesses-expanded-seeds.txt`;
 97    fs.writeFileSync(seedFile, CORE_SEEDS.join('\n'), 'utf-8');
 98    logger.success(`Created seed file with ${CORE_SEEDS.length} seeds`);
 99  
100    // Generate expanded keywords using DataForSEO Labs API
101    const csvPath = `${countryDir}/businesses-search-volume-optimized.csv`;
102  
103    try {
104      logger.info(`Generating keywords for ${countryCode}...`);
105      const stats = await generateSearchVolumeCSVOptimized(seedFile, countryCode, csvPath);
106  
107      logger.success(`Generated ${stats.totalKeywords} keywords`);
108      logger.info(`  - Seeds: ${stats.seedCount}`);
109      logger.info(`  - Related found: ${stats.totalRelated}`);
110      logger.info(`  - Already have volume: ${stats.haveVolume}`);
111      logger.info(`  - Needed Overview: ${stats.needOverview}`);
112  
113      // Apply filtering
114      const csvContent = fs.readFileSync(csvPath, 'utf-8');
115      const keywords = parse(csvContent, { columns: true, skip_empty_lines: true });
116  
117      const kwList = keywords.map(k => k.keyword);
118      const filtered = await filterKeywordsPreOverview(
119        kwList,
120        'en',
121        150, // Allow more keywords through
122        countryCode
123      );
124  
125      logger.success(
126        `Filtered: ${filtered.filtered_keywords.length} kept, ${filtered.removed.length} removed`
127      );
128  
129      // Save filtered results
130      const finalPath = `${countryDir}/businesses-final-filtered.csv`;
131      const finalData = keywords
132        .filter(k => filtered.filtered_keywords.includes(k.keyword))
133        .sort((a, b) => parseInt(b.search_volume) - parseInt(a.search_volume));
134  
135      const csvData = stringify(finalData, {
136        header: true,
137        columns: [
138          { key: 'keyword', header: 'keyword' },
139          { key: 'search_volume', header: 'search_volume' },
140          { key: 'competition', header: 'competition' },
141          { key: 'cpc_low', header: 'cpc_low' },
142          { key: 'cpc_high', header: 'cpc_high' },
143          { key: 'related_to', header: 'related_to' },
144          { key: 'country_code', header: 'country_code' },
145        ],
146      });
147  
148      fs.writeFileSync(finalPath, csvData, 'utf-8');
149  
150      const finalCount = finalData.length;
151      const status = finalCount >= 50 ? '✓' : '✗';
152      logger.success(
153        `${status} Final count: ${finalCount} keywords ${finalCount >= 50 ? '(TARGET MET)' : '(BELOW TARGET)'}`
154      );
155  
156      // Clean up seed file
157      fs.unlinkSync(seedFile);
158  
159      return {
160        country: countryCode,
161        before: country.current,
162        after: finalCount,
163        generated: stats.totalKeywords,
164        removed: filtered.removed.length,
165        success: finalCount >= 50,
166      };
167    } catch (error) {
168      logger.error(`Error expanding ${countryCode}: ${error.message}`);
169      return {
170        country: countryCode,
171        error: error.message,
172        success: false,
173      };
174    }
175  }
176  
177  /**
178   * Main function
179   */
180  async function main() {
181    console.log('Expanding keywords for countries with <50 final keywords\n');
182    console.log(`Processing ${COUNTRIES_TO_EXPAND.length} countries...\n`);
183  
184    const results = [];
185  
186    for (const country of COUNTRIES_TO_EXPAND) {
187      const result = await expandCountry(country);
188      results.push(result);
189    }
190  
191    // Summary
192    console.log(`\n${'='.repeat(60)}`);
193    console.log('SUMMARY');
194    console.log('='.repeat(60));
195  
196    const successful = results.filter(r => r.success).length;
197    const failed = results.filter(r => !r.success).length;
198  
199    console.log(`\n✓ Successful: ${successful}/${results.length}`);
200    console.log(`✗ Below target: ${failed}/${results.length}\n`);
201  
202    console.log('Per-country results:');
203    for (const result of results) {
204      if (result.error) {
205        console.log(`  ${result.country}: ERROR - ${result.error}`);
206      } else {
207        const status = result.success ? '✓' : '✗';
208        console.log(
209          `  ${status} ${result.country}: ${result.before} → ${result.after} (${result.generated} generated, ${result.removed} removed)`
210        );
211      }
212    }
213  
214    console.log('\nExpansion complete!');
215  }
216  
217  main().catch(error => {
218    logger.error(`Fatal error: ${error.message}`);
219    process.exit(1);
220  });