SPB Git

spb/ka4 Public

ka4 — explorateur structuré du web québécois (édition agressive, Groupe KA). Crawling concurrent, Claude Sonnet+Haiku, Firecrawl/Scrapfly, graphe de connaissances.

Python 97.8% Shell 2.2%
7.1 KB · 134 lines python
Raw Blame History
1# Author: Simon-Pierre Boucher2# Contact: contact@spboucher.ai3"""Extraction d'un GRAPHE DE CONNAISSANCES orienté CRÉATEURS / INFLUENCEURS (ka6).45Objectif initial du flagship : cartographier l'écosystème des créateurs de contenu6et influenceurs québécois — leurs plateformes, handles, nombre d'abonnés, niche,7langues — et leurs relations (agence qui les représente, marques avec qui ils8collaborent, plateformes où ils publient, collectifs dont ils sont membres).910On extrait aussi les entités classiques (entreprises, organisations, agences).11Données PUBLIQUES uniquement ; aucune donnée sensible ou privée.12"""1314from __future__ import annotations1516from typing import Any1718from anthropic import Anthropic1920GRAPH_TOOL = {21    "name": "record_graph",22    "description": "Enregistre les entités (dont créateurs/influenceurs) et leurs relations.",23    "input_schema": {24        "type": "object",25        "properties": {26            "entities": {27                "type": "array",28                "items": {29                    "type": "object",30                    "properties": {31                        "temp_id": {"type": "string", "description": "Identifiant local unique (e1, e2…) référencé par les relations"},32                        "type": {"type": "string",33                                 "enum": ["creator", "business", "organization", "agency", "brand", "person", "website"],34                                 "description": "Utilise 'creator' pour un influenceur/créateur de contenu, 'agency' pour une agence d'influence/talent, 'brand' pour une marque."},35                        "name": {"type": "string"},36                        "canonical_name": {"type": "string"},37                        "handle": {"type": "string", "description": "Pseudo principal (ex. @nomcreateur)"},38                        "platform": {"type": "string", "description": "Plateforme principale (Instagram, TikTok, YouTube, Twitch, X, Facebook, Snapchat…)"},39                        "followers": {"type": "string", "description": "Nb d'abonnés principal (ex. '125K', '1.2M', 34000)"},40                        "niche": {"type": "string", "description": "Créneau/thématique (mode, beauté, gaming, cuisine, humour, lifestyle, sport, tech, voyage, famille, musique…)"},41                        "languages": {"type": "array", "items": {"type": "string"}, "description": "Langues du contenu (fr, en…)"},42                        "sector": {"type": "string", "description": "Secteur pour les entreprises/agences/marques"},43                        "description": {"type": "string"},44                        "website": {"type": "string"},45                        "email": {"type": "string"},46                        "phone": {"type": "string"},47                        "city": {"type": "string"},48                        "region": {"type": "string", "description": "Ville/région du Québec"},49                        "social_links": {50                            "type": "array",51                            "items": {52                                "type": "object",53                                "properties": {54                                    "platform": {"type": "string"},55                                    "url": {"type": "string"},56                                    "followers": {"type": "string", "description": "Abonnés sur CETTE plateforme si connu"},57                                },58                                "required": ["platform", "url"],59                            },60                        },61                        "confidence": {"type": "number", "minimum": 0, "maximum": 1},62                    },63                    "required": ["temp_id", "type", "name"],64                },65            },66            "relations": {67                "type": "array",68                "items": {69                    "type": "object",70                    "properties": {71                        "from": {"type": "string"},72                        "to": {"type": "string"},73                        "type": {74                            "type": "string",75                            "enum": ["REPRESENTED_BY", "COLLABORATES_WITH", "CREATES_ON", "SPONSORED_BY",76                                     "PROMOTES", "MEMBER_OF", "MANAGES", "APPEARS_WITH",77                                     "WORKS_AT", "FOUNDER_OF", "PARTNER_OF", "AFFILIATED_WITH"],78                            "description": "Ex : créateur REPRESENTED_BY agence ; créateur COLLABORATES_WITH marque ; créateur CREATES_ON plateforme ; créateur MEMBER_OF collectif.",79                        },80                        "role": {"type": "string", "description": "Précision (ex. 'ambassadrice', 'campagne 2025', 'chaîne principale')"},81                        "confidence": {"type": "number", "minimum": 0, "maximum": 1},82                    },83                    "required": ["from", "to", "type"],84                },85            },86        },87        "required": ["entities"],88    },89}9091SYSTEM = (92    "Tu es l'extracteur du bot FLAGSHIP ka6 du Groupe KA. Objectif initial : cartographier "93    "l'écosystème des CRÉATEURS DE CONTENU et INFLUENCEURS québécois à partir de pages publiques "94    "(répertoires, palmarès, rosters d'agences, articles, pages de marques).\n\n"95    "Pour chaque créateur/influenceur (type 'creator') extrais : nom, handle (@), plateforme "96    "principale, nombre d'abonnés, niche/thématique, langues, région, et TOUTES ses plateformes "97    "dans social_links (avec abonnés par plateforme si indiqués).\n"98    "Modélise les RELATIONS : REPRESENTED_BY (agence de talent/influence), COLLABORATES_WITH / "99    "SPONSORED_BY / PROMOTES (marques), CREATES_ON (plateformes), MEMBER_OF (collectif/réseau), "100    "MANAGES (gérant/agence -> créateur).\n"101    "Extrais aussi agences ('agency'), marques ('brand'), entreprises/organisations pertinentes.\n"102    "Règles : données PUBLIQUES uniquement ; priorise le Québec ; n'invente rien (laisse vide si "103    "absent) ; donne un temp_id unique par entité et référence-le dans les relations ; confidence "104    "réaliste. Réponds uniquement via l'outil record_graph."105)106107108class Extractor:109    def __init__(self, client: Anthropic, model: str):110        self.client = client111        self.model = model112113    def extract_graph(self, url: str, title: str, markdown: str) -> dict[str, Any]:114        if not markdown or not markdown.strip():115            return {"entities": [], "relations": []}116        content = markdown[:20000]117        resp = self.client.messages.create(118            model=self.model,119            max_tokens=3500,120            system=SYSTEM,121            tools=[GRAPH_TOOL],122            tool_choice={"type": "tool", "name": "record_graph"},123            messages=[{124                "role": "user",125                "content": f"URL: {url}\nTitre: {title}\n\nContenu (markdown):\n{content}",126            }],127        )128        for block in resp.content:129            if block.type == "tool_use" and block.name == "record_graph":130                data = block.input or {}131                return {"entities": data.get("entities", []) or [],132                        "relations": data.get("relations", []) or []}133        return {"entities": [], "relations": []}134