sourcefinder/docs/ARCHITECTURE_DECISIONS.md
Alexis Trouvé a7bd6115b7
Some checks failed
SourceFinder CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
SourceFinder CI/CD Pipeline / Unit Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Integration Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Performance Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Code Coverage Report (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (16.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (18.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Build & Deployment Validation (20.x) (push) Has been cancelled
SourceFinder CI/CD Pipeline / Regression Tests (push) Has been cancelled
SourceFinder CI/CD Pipeline / Security Audit (push) Has been cancelled
SourceFinder CI/CD Pipeline / Notify Results (push) Has been cancelled
feat: Implémentation complète du système SourceFinder avec tests
- Architecture modulaire avec injection de dépendances
- Système de scoring intelligent multi-facteurs (spécificité, fraîcheur, qualité, réutilisation)
- Moteur anti-injection 4 couches (preprocessing, patterns, sémantique, pénalités)
- API REST complète avec validation et rate limiting
- Repository JSON avec index mémoire et backup automatique
- Provider LLM modulaire pour génération de contenu
- Suite de tests complète (Jest) :
  * Tests unitaires pour sécurité et scoring
  * Tests d'intégration API end-to-end
  * Tests de sécurité avec simulation d'attaques
  * Tests de performance et charge
- Pipeline CI/CD avec GitHub Actions
- Logging structuré et monitoring
- Configuration ESLint et environnement de test

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 23:06:10 +08:00

13 KiB
Raw Permalink Blame History

🏗️ ARCHITECTURE DECISIONS - SourceFinder

Synthèse complète des décisions techniques prises lors de l'analyse


🎯 1. POURQUOI EXPRESS.JS ?

Alternatives considérées

Framework Avantages Inconvénients
Express.js Mature, écosystème, flexibilité Plus verbeux, configuration manuelle
Fastify Performance supérieure, TypeScript natif Écosystème plus petit
Koa.js Moderne (async/await), léger Moins de middleware prêts
NestJS Enterprise-ready, TypeScript, DI Complexité, courbe d'apprentissage

Décision : Express.js

Justifications clés :

  1. Écosystème mature pour nos besoins spécifiques
// Middleware critiques disponibles immédiatement
app.use(helmet());              // Sécurité headers
app.use(rateLimit());           // Rate limiting Redis
app.use(cors());                // CORS pour multi-clients
  1. Flexibilité architecture microservice
// Pattern service-oriented parfait pour notre CDC
const scoringService = require('./services/scoringService');
const securityService = require('./services/securityService');

app.post('/api/v1/news/search', async (req, res) => {
  // Validation → Scoring → Security → Response
  const results = await scoringService.searchAndScore(req.body);
  const sanitized = await securityService.validateContent(results);
  res.json(sanitized);
});
  1. Performance adaptée à nos contraintes
CDC requirement: "Réponses < 5 secondes"
Express throughput: ~15,000 req/sec (largement suffisant)
Notre bottleneck: Web scraping & DB queries, pas le framework
  1. Middleware essentiels pour la sécurité
// Anti-prompt injection pipeline
app.use('/api/v1/news', [
  authMiddleware,           // API key validation
  rateLimitingMiddleware,   // Prevent abuse
  contentValidation,        // Input sanitization
  promptInjectionDetection  // Notre middleware custom
]);

Express overhead = 0.3% du temps total → négligeable.


🗄️ 2. STOCKAGE : JSON MODULAIRE vs BASES TRADITIONNELLES

Problématique initiale

CDC prévoyait MongoDB/PostgreSQL, mais besoin de simplicité et modularité.

Décision : JSON par défaut, interface modulaire

Architecture retenue :

// Interface NewsStockRepository (adaptable JSON/MongoDB/PostgreSQL)
{
  id: String,
  url: String (unique),
  title: String,
  content: String,
  content_hash: String,

  // Classification
  race_tags: [String], // ["352-1", "bergers", "grands_chiens"]
  angle_tags: [String], // ["legislation", "sante", "comportement"]
  universal_tags: [String], // ["conseils_proprietaires", "securite"]

  // Scoring
  freshness_score: Number,
  quality_score: Number,
  specificity_score: Number,
  reusability_score: Number,
  final_score: Number,

  // Usage tracking
  usage_count: Number,
  last_used: Date,
  created_at: Date,
  expires_at: Date,

  // Metadata
  source_domain: String,
  source_type: String, // "premium", "standard", "fallback"
  language: String,
  status: String // "active", "expired", "blocked"
}

// Implémentation par défaut: JSON files avec index en mémoire
// Migration possible vers MongoDB/PostgreSQL sans changement de code métier

Avantages approche modulaire :

  1. Simplicité : Pas de setup MongoDB/PostgreSQL pour débuter
  2. Performance : Index en mémoire pour recherches rapides
  3. Flexibilité : Change de DB sans toucher la logique métier
  4. Évolutivité : Migration transparente quand nécessaire
  5. Développement : Focus sur la logique scoring/scraping d'abord

Pattern Repository avec adaptateurs :

// Interface abstraite
class NewsStockRepository {
  async findByRaceCode(raceCode) { throw new Error('Not implemented'); }
  async findByScore(minScore) { throw new Error('Not implemented'); }
  async save(newsItem) { throw new Error('Not implemented'); }
}

// Implémentation JSON
class JSONStockRepository extends NewsStockRepository {
  constructor(dataPath) {
    this.dataPath = dataPath;
    this.memoryIndex = new Map(); // Performance
  }
}

// Futures implémentations
class MongoStockRepository extends NewsStockRepository { ... }
class PostgreSQLStockRepository extends NewsStockRepository { ... }

🕷️ 3. STRATÉGIE SCRAPING : ÉVOLUTION DES APPROCHES

3.1 Approche initiale : Scraping traditionnel

Complexité sous-estimée identifiée :

Partie "facile" (20% du travail)

// Scraping basique - ça marche en 30 minutes
const puppeteer = require('puppeteer');
const cheerio = require('cheerio');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://30millionsdamis.fr');
const html = await page.content();
const $ = cheerio.load(html);
const articles = $('.article-title').text();

Défis moyens (30% du travail)

  • Sites avec JavaScript dynamique
  • Rate limiting intelligent
  • Parsing de structures variables

Complexité élevée (50% du travail)

  • Anti-bot sophistiqués (Cloudflare, reCAPTCHA)
  • Sites spécialisés = plus protégés
  • Parsing fragile (structure change = casse tout)
  • Gestion d'erreurs complexe

Vrais cauchemars (problèmes récurrents)

Semaine 1: 50 sources fonctionnent
Semaine 3: 30 millions d'Amis change sa structure → cassé
Semaine 5: Wamiz ajoute reCAPTCHA → cassé
Semaine 8: Centrale Canine bloque notre IP → cassé

Temps réaliste : 4-6 semaines (vs 2 semaines budgétées dans CDC)

Facteur aggravant : Les sources les plus valables (clubs race, sites vétérinaires) sont souvent les plus protégées.

3.2 Approche LLM Providers

Concept analysé :

// Au lieu de scraper + parser
const rawHtml = await puppeteer.scrape(url);
const content = cheerio.parse(rawHtml);

// On aurait directement
const news = await llmProvider.searchNews({
  query: "Berger Allemand actualités 2025",
  sources: ["specialized", "veterinary", "official"],
  language: "fr"
});

Avantages :

  • Simplicité technique
  • Contenu pré-traité
  • Évite problèmes légaux
  • Pas de maintenance scraping

Questions critiques non résolues :

  • Quels providers peuvent cibler sources spécialisées ?
  • Fraîcheur données (< 7 jours requirement) ?
  • Contrôle anti-prompt injection ?
  • Coût scaling avec volume ?

3.3 Approche hybride : LLM + Scraping intelligent

Concept retenu :

// LLM génère les selectors automatiquement
const scrapingPrompt = `
Analyze this HTML structure and extract news articles:
${htmlContent}

Return JSON with selectors for:
- Article titles
- Article content
- Publication dates
- Article URLs
`;

const selectors = await llm.generateSelectors(htmlContent);
// → { title: '.article-h2', content: '.post-content', date: '.publish-date' }

Avantages hybride :

  1. Auto-adaptation aux changements - LLM s'adapte aux nouvelles structures
  2. Onboarding rapide nouvelles sources - Pas besoin de configurer selectors
  3. Content cleaning intelligent - LLM nettoie le contenu

Architecture hybride :

class IntelligentScrapingService {
  async scrapeWithLLM(url) {
    // 1. Scraping technique classique
    const html = await puppeteer.getPage(url);

    // 2. LLM analyse la structure
    const analysis = await llm.analyzePageStructure(html);

    // 3. Extraction basée sur analyse LLM
    const content = await this.extractWithLLMGuidance(html, analysis);

    // 4. Validation/nettoyage par LLM
    return await llm.validateAndClean(content);
  }
}

Coût estimé :

HTML page = ~50KB
LLM analysis = ~1000 tokens input + 200 tokens output
Cost per page ≈ $0.01-0.02 (GPT-4)

50 sources × 5 pages/jour = 250 scrapes/jour
250 × $0.015 = $3.75/jour = ~$110/mois

🥷 4. TECHNIQUES ANTI-DÉTECTION GRATUITES

Contrainte budget

  • LLM providers payants OK
  • Proxies payants (~50-100€/mois)
  • APIs externes
  • Services tiers

Arsenal gratuit développé

1. Stealth Browser Framework

const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

// Plugin qui masque TOUS les signaux Puppeteer
puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({
  headless: 'new', // Nouveau mode headless moins détectable
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-blink-features=AutomationControlled',
    '--disable-features=VizDisplayCompositor'
  ]
});

2. Randomisation comportementale

const humanLikeBehavior = {
  async randomDelay() {
    const delay = Math.random() * 2000 + 500; // 0.5-2.5s
    await new Promise(r => setTimeout(r, delay));
  },

  async humanScroll(page) {
    // Scroll irrégulier comme un humain
    for (let i = 0; i < 3; i++) {
      await page.evaluate(() => {
        window.scrollBy(0, Math.random() * 300 + 200);
      });
      await this.randomDelay();
    }
  }
};

3. TOR rotation gratuite

// Technique controversée mais légale : TOR rotation
const tor = require('tor-request');

const torRotation = {
  async getNewTorSession() {
    // Reset circuit TOR = nouvelle IP
    await tor.renewTorSession();
    return tor; // Nouveau circuit, nouvelle IP
  }
};

4. Browser fingerprint randomization

const freeFingerprinting = {
  async randomizeEverything(page) {
    // Timezone aléatoire
    await page.evaluateOnNewDocument(() => {
      const timezones = ['Europe/Paris', 'Europe/London', 'Europe/Berlin'];
      const tz = timezones[Math.floor(Math.random() * timezones.length)];
      Object.defineProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', {
        value: () => ({ timeZone: tz })
      });
    });

    // Canvas fingerprint randomization
    await page.evaluateOnNewDocument(() => {
      const getContext = HTMLCanvasElement.prototype.getContext;
      HTMLCanvasElement.prototype.getContext = function(type) {
        if (type === '2d') {
          const context = getContext.call(this, type);
          const originalFillText = context.fillText;
          context.fillText = function() {
            // Ajouter micro-variation invisible
            arguments[1] += Math.random() * 0.1;
            return originalFillText.apply(this, arguments);
          };
          return context;
        }
        return getContext.call(this, type);
      };
    });
  }
};

5. Distributed scraping gratuit

// Utiliser plusieurs VPS gratuits
const distributedScraping = {
  freeVPSProviders: [
    'Oracle Cloud Always Free (ARM)',
    'Google Cloud 3 months free',
    'AWS Free Tier 12 months',
    'Heroku free dynos',
    'Railway.app free tier'
  ],

  async distributeLoad() {
    // Chaque VPS scrape quelques sites
    // Coordination via base commune (notre JSON store)
    const tasks = this.splitScrapeTargets();
    return this.deployToFreeVPS(tasks);
  }
};

Stack gratuit complet retenu

const freeStack = {
  browser: 'puppeteer-extra + stealth (gratuit)',
  proxies: 'TOR rotation + free proxy scrapers',
  userAgents: 'Scraping de bases UA gratuites',
  timing: 'Analysis patterns gratuite',
  fingerprinting: 'Randomization manuelle',
  distribution: 'VPS free tiers',
  storage: 'JSON local (déjà prévu)',
  cache: 'Redis local (gratuit)',
  llm: 'OpenAI/Claude payant (accepté)'
};

Performance attendue

Technique Taux succès Maintenance
TOR + stealth 70-80% Moyenne
Free proxies 40-60% Haute
Fingerprint random +15% Basse
LLM evasion +20% Basse
Distributed VPS +25% Haute

Résultat combiné : ~80-85% succès (vs 95% avec proxies payants)


🎯 DÉCISIONS FINALES ARCHITECTURE

1. Framework : Express.js

  • Écosystème mature pour sécurité
  • Middleware anti-prompt injection
  • Performance suffisante pour nos besoins

2. Stockage : JSON modulaire

  • Interface Repository abstraite
  • JSON par défaut, migration path MongoDB/PostgreSQL
  • Index en mémoire pour performance

3. Scraping : Hybride LLM + Techniques gratuites

  • LLM pour intelligence et adaptation
  • Puppeteer-extra + stealth pour technique
  • TOR + fingerprinting pour anti-détection
  • Budget : 0€ infrastructure + coût LLM tokens

4. Architecture globale

[API Request] → [Auth/Rate Limiting] → [Stock Search JSON] → [LLM-Guided Scraping if needed] → [Intelligent Scoring] → [Anti-injection Validation] → [Filtered Results]

Coût total infrastructure : 0€/mois Efficacité attendue : 80-85% Temps développement : Respecte budget 155h

Cette architecture permet de démarrer rapidement avec un budget minimal tout en gardant la flexibilité d'évolution vers des solutions plus robustes si le projet scale.


Synthèse des décisions techniques prises lors des échanges du 15/09/2025