Nulmeting AI-zichtbaarheid

Meet hoe vaak ChatGPT, Gemini en andere AI-zoekmachines je merk noemen, op welke plaats en op basis van welke bronnen.

.zip, 9,4 kBversie 1.0.0bijgewerkt op

Wat doet deze skill?

Steeds meer mensen vragen een AI-zoekmachine welke aanbieder ze moeten kiezen. Of je merk in dat antwoord staat, en op welke plaats, zie je niet in Search Console of Analytics. Deze skill maakt daar een eerste meting van: een vaste set vragen, gesteld aan de motoren die voor jouw markt tellen, met per antwoord de genoemde merken, hun volgorde en de geciteerde bronnen.

Het resultaat is een nulmeting. Het protocol ligt vast, zodat je over een paar maanden dezelfde meting herhaalt en ziet wat er echt verschoven is.

Hoe werkt het?

  1. De skill legt je merk, naamvarianten, domeinen, concurrenten, markt en motoren vast.
  2. Hij stelt een promptset van 15 tot 30 vragen voor, verdeeld over categorie-, probleem-, vergelijkings- en prijsvragen, plus een paar merkvragen die apart blijven. Jij keurt de set goed.
  3. Hij verzamelt de antwoorden: via een connector als die er is (met een kostenraming vooraf), via een export uit je AI-visibilitytool, of via antwoorden die je zelf plakt.
  4. Per antwoord leest hij de aanbevolen merken in volgorde en de bronlinks uit.
  5. Een script scoort elk antwoord op positie, berekent de score per motor en per cluster, de share of voice en de bronnenanalyse, en meet hoeveel de antwoorden tussen twee runs verschillen.
  6. Hij schrijft een kort rapport met protocol, hoofdcijfers, gaten en bronnen.

Wat je krijgt

  • Een Excel-werkboek met score, vermeld-aandeel en gemiddelde positie, totaal en per motor en cluster.
  • Een share of voice tegenover alle genoemde aanbieders.
  • Een bronnentabel: welke domeinen de motoren citeren en welke samengaan met een vermelding van je merk.
  • Een tab met controles, zodat geen antwoord stil verloren gaat.
  • Een rapport dat als basis dient voor de volgende meting.

Goed om te weten

AI-antwoorden verschillen per run, account, locatie en dag. Een nulmeting is een momentopname, en de skill toont hoe groot die wisseling is. Ontbrekende antwoorden worden nooit ingevuld met schattingen. Een gewone zoekopdracht of SERP-check telt niet als AI-meting; de skill gebruikt die hoogstens als aanvulling en houdt ze apart. Met een connector kost elke vraag een paar cent, en je krijgt eerst een raming. Je data blijft in je eigen Claude-gesprek.

Zo start je hem

Typ een van deze zinnen in een nieuw gesprek, of iets gelijkaardigs.

  • Maak een nulmeting van de AI-zichtbaarheid van ons merk tegenover drie concurrenten.
  • Stel een promptset op om te meten of ChatGPT en Gemini ons aanbevelen.
  • Hier zijn de antwoorden die ik uit ChatGPT kopieerde. Scoor ze en zeg welke bronnen meetellen.

Wat er in de zip zit

4 bestanden, allemaal gewone tekst. Lees ze gerust na voor je de skill installeert.

ai-zichtbaarheid-nulmeting/SKILL.md9,1 kB
---
name: ai-zichtbaarheid-nulmeting
description: Maakt een nulmeting van de AI-zichtbaarheid van een merk in ChatGPT, Gemini, Perplexity, Copilot of Google AI Overviews en AI Mode. Stelt een promptset op, verzamelt per prompt de antwoorden (via een connector zoals DataForSEO AI Optimization als die er is, anders via handmatig geplakte antwoorden of een export uit een AI-visibilitytool), telt merkvermeldingen, posities en geciteerde bronnen, en levert een Excel-werkboek met score per motor en cluster, share of voice tegenover concurrenten, een bronnenanalyse en een kort rapport als basis voor herhaalmetingen. Gebruik deze skill wanneer iemand vraagt hoe zichtbaar een merk is in AI-antwoorden, een AI-zichtbaarheidsmeting, GEO-nulmeting of baseline wil, wil weten of ChatGPT of Gemini het merk aanbeveelt, welke bronnen AI citeert of hoe het merk zich verhoudt tot concurrenten in AI-zoekmachines.
---

# Nulmeting AI-zichtbaarheid

Je meet hoe vaak en hoe hoog een merk wordt genoemd in antwoorden van AI-zoekmachines op echte gebruikersvragen, welke concurrenten er in plaats van het merk staan, en welke bronnen de motoren citeren. Het resultaat is een nulmeting: een vast protocol dat later herhaald wordt.

De zwaarste fout in dit werk is ontbrekende antwoorden opvullen met plausibele waarden. Doe dat nooit. Niet gemeten is niet gemeten, en dat staat zo in de output.

## Stap 0: configuratie vastleggen

Vraag of leid af, en bewaar als `config.json` (sjabloon in `assets/config-sjabloon.json`):

- **Merk**, domein(en) en alle naamvarianten die als het merk tellen: submerken, oude handelsnamen, schrijfwijzen, "merk + plaatsnaam". Vraag hier expliciet naar. AI koppelt submerken en oude namen vaak niet aan het hoofdmerk, en dat gat is zelf een bevinding.
- **Concurrenten** (3 tot 8) met hun varianten. Onbekende aanbieders in de antwoorden tel je ook, ze komen vanzelf in de tab Merken.
- **Markt en taal**: land, taal van de prompts, eventueel regio's.
- **Motoren**: standaard ChatGPT en Gemini met websearch. Voeg Perplexity, Copilot, AI Overviews of AI Mode toe als de gebruiker dat wil en de verzamelroute het toelaat.
- **Runs**: 2 per prompt per motor. Eén run is instabiel; antwoorden wisselen tussen runs.
- **Generieke woorden** die je negeert bij het groeperen van aanbiedersnamen (rechtsvormen, sectorwoorden), en plaatsnamen die als suffix voorkomen.

## Stap 1: promptset opstellen

Stel 15 tot 30 prompts voor, in de taal van de doelgroep, geschreven zoals een mens ze typt. Verdeel ze over clusters:

| Cluster | Voorbeeldvorm | Aandeel |
| --- | --- | --- |
| Categorie | "Wat is de beste [dienst] voor [doelgroep] in [land]?" | ongeveer 30% |
| Probleem | "Hoe los ik [probleem] op zonder [nadeel]?" | ongeveer 20% |
| Vergelijking en alternatief | "Wat zijn alternatieven voor [concurrent]?", "[A] of [B]?" | ongeveer 20% |
| Prijs en voorwaarden | "Wat kost [dienst] gemiddeld voor [situatie]?" | ongeveer 15% |
| Lokaal of regionaal (optioneel) | "Welke [aanbieder] in [plaats] is aan te raden?" | naar behoefte |
| Merk (apart) | "Wat is [merk]?", "Is [merk] betrouwbaar?" | 2 tot 3 prompts |

Regels:

- Geef elke prompt een id (P1, P2, ...) en een cluster.
- Gebruik bestaande vragen waar mogelijk: Search Console-queries in vraagvorm, People Also Ask, vragen uit sales of klantendienst.
- Merkprompts meten hoe de AI het merk beschrijft, niet of het merk gevonden wordt. Zet hun id's in `merkprompts`: ze tellen niet mee in de score en je rapporteert ze apart.
- Laat de gebruiker de promptset goedkeuren voor je verzamelt. Na de nulmeting liggen de prompts vast; een gewijzigde prompt is een nieuwe meting.

## Stap 2: antwoorden verzamelen

Kies de route die beschikbaar is en zeg welke je gebruikte. Alle routes leveren dezelfde tabel op (sjabloon in `assets/antwoorden-sjabloon.csv`), een rij per antwoord:

`prompt_id, prompt, cluster, motor, run, datum, web_search, antwoord_tekst, merken_in_volgorde, geciteerde_urls`

**Route A: connector.** Met een DataForSEO-connector: AI Optimization, LLM Responses (`ai_optimization_llm_response` of gelijkaardig), per motor het actuele model, websearch aan, geen temperature-instellingen. Werkwijze:

- Raam de kost vooraf (aantal prompts x motoren x runs x prijs per call) en vraag akkoord. Prijzen veranderen; noem ze als raming.
- Bewaar elk antwoord meteen volledig: antwoordtekst, exacte modelnaam uit de respons, of er echt gezocht is, fan-out-queries als de respons ze geeft, en de geciteerde URL's. Bij Gemini staat het echte domein vaak in de titel van de annotatie, niet in de redirect-URL. Bij ChatGPT haal je `utm_source`-parameters weg.
- Bij een limiet- of kredietfout stop je meteen en markeer je de meting als onvolledig. Andere fouten: een keer opnieuw proberen, dan registreren als niet gemeten.
- Werk je met veel prompts, verwerk ze in groepen en houd de volledige antwoordteksten buiten de chat; schrijf ze naar een bestand.

**Route B: export uit een AI-visibilitytool.** Veel tools exporteren prompt, motor, antwoord en bronnen. Zet de kolommen om naar de tabel hierboven. Neem de datum en de modelnaam over als ze erin staan.

**Route C: handmatig.** De gebruiker stelt elke prompt in een nieuw gesprek, liefst uitgelogd of in een privévenster, zonder geheugen of eigen instructies, en plakt per antwoord de volledige tekst en de bronlinks. Geef hem de promptlijst als tabel om in te vullen. Vraag welke motor, welk account (gratis of betaald) en welke datum.

**Wat geen meting is.** Web search of een SERP-check geeft geen AI-antwoord. Gebruik die alleen als aanvulling: welke pagina's ranken op de vragen of fan-out-queries, en staat het merk daar. Rapporteer dat apart en scoor het nooit als AI-zichtbaarheid.

## Stap 3: antwoorden uitlezen

Per antwoord vul je in:

- **merken_in_volgorde**: alleen aanbieders, geen portalen of vergelijkingssites, in de volgorde waarin het antwoord ze aanbeveelt, elk merk een keer, gescheiden door ` | `. Naam zonder plaatssuffix.
- **geciteerde_urls**: alle bronlinks, gescheiden door ` | `.
- **web_search**: ja of nee, eerlijk. Een motor zoekt soms niet, ook als dat gevraagd is.

Lees zelf; tel niet op trefwoorden. Een merk dat genoemd wordt als "te vermijden" staat in de lijst, maar noteer dat in je rapport.

## Stap 4: scoren

```bash
python3 scripts/scoor.py --antwoorden antwoorden.csv --config config.json --out nulmeting.xlsx
```

Het script vraagt alleen `openpyxl` en:

- herberekent de merkpositie uit `merken_in_volgorde` met alle varianten, en groepeert aanbieders zonder plaatssuffix en generieke woorden;
- geeft per antwoord een gewicht: plaats 1 = 1,0; plaats 2 tot 3 = 0,7; plaats 4 tot 5 = 0,4; lager = 0,2; afwezig = 0;
- berekent per motor, per cluster en in totaal: score (gemiddeld gewicht), aandeel antwoorden met vermelding, gemiddelde positie als vermeld, aandeel antwoorden met het eigen domein als bron;
- maakt een share of voice over alle genoemde aanbieders en een bronnentabel: per domein hoe vaak het geciteerd wordt, per motor, en hoe vaak het merk vermeld is als dat domein geciteerd wordt, tegenover de basiskans;
- telt hoeveel prompt-motorparen tussen runs wisselen tussen vermeld en niet vermeld (de herhaalruis);
- lijst controles: lege antwoorden, dubbele rijen, en antwoorden waar het merk in de tekst staat maar niet in de lijst. Los die op voor je rapporteert.

Gebruik de cijfers uit de JSON-uitvoer van het script, niet je eigen hoofdrekening.

## Stap 5: rapport

Lever het werkboek en een kort rapport (in de chat, of als document of pagina als de gebruiker dat vraagt) met:

1. **Protocol**: prompts letterlijk, motoren en modelnamen, aantal runs, datum, route, gewichten.
2. **Hoofdcijfers**: score en vermeld-aandeel totaal en per motor; positie tegenover de concurrenten.
3. **Waar het merk ontbreekt**: clusters en prompts met score 0, en wie daar wel genoemd wordt.
4. **Bronnen**: de domeinen die het vaakst geciteerd worden, per motor; welke ervan samengaan met een vermelding van het merk; staat het eigen domein erbij. Toets patronen in de data, neem ze niet aan. Een veelgezien patroon is dat de ene motor vooral op review- en vergelijkingssites steunt en de andere meer op de eigen site van de aanbieder.
5. **Merkprompts**: hoe de AI het merk beschrijft, met opvallende fouten of verouderde informatie.
6. **Duiding en beperkingen**: wat waargenomen is (antwoordtekst, bronnen) en wat afgeleid (positie, score, share of voice, samenhang met bronnen). AI-antwoorden verschillen per run, account, locatie en dag; dit is een momentopname. Noem de herhaalruis uit Stap 4.
7. **Herhaling**: dezelfde prompts, motoren, runs en route. Lees bij de volgende meting alleen verschillen die groter zijn dan de herhaalruis als signaal.

Valkuilen:

- Submerken en oude handelsnamen die de AI niet aan het merk koppelt: meet ze mee als variant en rapporteer het gat apart.
- Motoren lezen plaatsnamen soms verkeerd (een gemeente in het verkeerde land). Markeer zulke antwoorden als meetartefact.
- Review- en vergelijkingsprofielen blijven bestaan na een sluiting of naamswijziging en geven dan spookzichtbaarheid.
- Personaliseer niet: geen ingelogd account met geschiedenis, of meld het als dat wel zo was.

Gemaakt door Depends (depends.be). Vrij te gebruiken en aan te passen.
ai-zichtbaarheid-nulmeting/assets/antwoorden-sjabloon.csv102 B
prompt_id,prompt,cluster,motor,run,datum,web_search,antwoord_tekst,merken_in_volgorde,geciteerde_urls
ai-zichtbaarheid-nulmeting/assets/config-sjabloon.json434 B
{
  "merk": "Voorbeeldmerk",
  "varianten": ["Voorbeeldmerk BV", "Oude handelsnaam", "Submerk"],
  "domeinen": ["voorbeeldmerk.be"],
  "concurrenten": {
    "Concurrent A": ["Concurrent A Groep"],
    "Concurrent B": []
  },
  "generieke_woorden": ["bv", "nv", "groep", "group"],
  "plaatsnamen": [],
  "markt": "België, Nederlandstalig",
  "motoren": ["ChatGPT", "Gemini"],
  "runs_per_prompt": 2,
  "merkprompts": ["P14", "P15"]
}
ai-zichtbaarheid-nulmeting/scripts/scoor.py11,3 kB
#!/usr/bin/env python3
"""Scoort een AI-zichtbaarheidsmeting uit een antwoordentabel en een config.json en schrijft een Excel-werkboek.

Antwoordentabel (CSV of XLSX), een rij per antwoord. Kolommen:
  prompt_id, prompt, cluster, motor, run, datum, web_search, antwoord_tekst, merken_in_volgorde, geciteerde_urls
merken_in_volgorde en geciteerde_urls: waarden gescheiden door ' | ', in de volgorde van het antwoord.

config.json: zie assets/config-sjabloon.json."""
import argparse, collections, csv, json, re, statistics
from urllib.parse import urlparse
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, PatternFill, Alignment

GEWICHT = [(1, 1.0), (3, 0.7), (5, 0.4)]  # plaats 1 = 1,0; 2-3 = 0,7; 4-5 = 0,4; lager = 0,2; afwezig = 0
MOTOR = {'chatgpt': 'ChatGPT', 'chat_gpt': 'ChatGPT', 'openai': 'ChatGPT', 'gpt': 'ChatGPT', 'gemini': 'Gemini', 'perplexity': 'Perplexity',
         'copilot': 'Copilot', 'claude': 'Claude', 'ai overview': 'AI Overviews', 'ai overviews': 'AI Overviews', 'aio': 'AI Overviews',
         'ai mode': 'AI Mode', 'google ai mode': 'AI Mode'}


def gewicht(pos):
    if not pos: return 0.0
    for grens, g in GEWICHT:
        if pos <= grens: return g
    return 0.2


def lees(pad):
    if pad.lower().endswith('.xlsx'):
        ws = load_workbook(pad, read_only=True, data_only=True).worksheets[0]
        rows = [['' if c is None else str(c) for c in r] for r in ws.iter_rows(values_only=True)]
    else:
        raw = open(pad, encoding='utf-8-sig').read()
        delim = ';' if raw.split('\n', 1)[0].count(';') > raw.split('\n', 1)[0].count(',') else ','
        rows = list(csv.reader(raw.splitlines(), delimiter=delim))
    head = [h.strip().lower() for h in rows[0]]
    return [dict(zip(head, r)) for r in rows[1:] if any(x.strip() for x in r)]


def lijst(s):
    return [x.strip() for x in str(s or '').split('|') if x.strip()]


def kern(naam, generiek):
    n = re.sub(r'[^\w ]', ' ', naam.lower())
    return ' '.join(w for w in n.split() if w not in generiek)


def domein(u):
    u = u.strip()
    if not u: return ''
    if '://' not in u: u = 'https://' + u
    d = urlparse(u).netloc.lower()
    return d[4:] if d.startswith('www.') else d


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--antwoorden', required=True); ap.add_argument('--config', required=True); ap.add_argument('--out', required=True)
    a = ap.parse_args()
    cfg = json.load(open(a.config, encoding='utf-8'))
    generiek = set(w.lower() for w in cfg.get('generieke_woorden', []))
    plaatsen = [p.lower() for p in cfg.get('plaatsnamen', [])]

    def zuiver(naam):
        n = naam.lower()
        for p in plaatsen: n = re.sub(rf'\b{re.escape(p)}\b', ' ', n)
        return kern(n, generiek)

    merk = cfg['merk']; varianten = {zuiver(v) for v in [merk] + cfg.get('varianten', [])} - {''}
    eigen_dom = {domein(d) for d in cfg.get('domeinen', [])}
    conc = {naam: {zuiver(v) for v in [naam] + vs} - {''} for naam, vs in cfg.get('concurrenten', {}).items()}
    merkprompts = set(cfg.get('merkprompts', []))

    def wie(naam):
        z = zuiver(naam)
        if z in varianten: return merk
        for c, vs in conc.items():
            if z in vs: return c
        return naam.strip()

    R = lees(a.antwoorden); fouten = []; rijen = []
    gezien = collections.Counter()
    for k, r in enumerate(R, start=2):
        m = MOTOR.get(r.get('motor', '').strip().lower(), r.get('motor', '').strip())
        if not r.get('antwoord_tekst', '').strip(): fouten.append(f'rij {k}: leeg antwoord ({r.get("prompt_id")}, {m}), telt niet mee')
        if not r.get('prompt_id'): fouten.append(f'rij {k}: geen prompt_id'); continue
        gezien[(r['prompt_id'], m, r.get('run', '1'))] += 1
        merken = []
        for x in lijst(r.get('merken_in_volgorde')):
            w = wie(x)
            if w not in merken: merken.append(w)
        pos = merken.index(merk) + 1 if merk in merken else None
        tekst = r.get('antwoord_tekst', '')
        if pos is None and any(re.search(rf'\b{re.escape(v)}\b', zuiver(tekst)) for v in varianten if len(v) > 2):
            fouten.append(f'rij {k}: merk staat in de antwoordtekst maar niet in merken_in_volgorde ({r["prompt_id"]}, {m}), controleer')
        doms = []
        for u in lijst(r.get('geciteerde_urls')):
            d = domein(u)
            if d and d not in doms: doms.append(d)
        rijen.append({'pid': r['prompt_id'], 'prompt': r.get('prompt', ''), 'cluster': r.get('cluster', '') or 'Geen cluster', 'motor': m, 'run': r.get('run', '1'),
                      'datum': r.get('datum', ''), 'ws': r.get('web_search', ''), 'merken': merken, 'pos': pos, 'g': gewicht(pos),
                      'doms': doms, 'eigen': any(d in eigen_dom or any(d.endswith('.' + e) for e in eigen_dom) for d in doms),
                      'merkprompt': r['prompt_id'] in merkprompts, 'leeg': not tekst.strip()})
    for key, n in gezien.items():
        if n > 1: fouten.append(f'dubbele rij: prompt {key[0]}, {key[1]}, run {key[2]} ({n} keer)')

    S = [r for r in rijen if not r['merkprompt'] and not r['leeg']]
    def blok(rr):
        if not rr: return {'antwoorden': 0}
        ps = [r['pos'] for r in rr if r['pos']]
        return {'antwoorden': len(rr), 'score': statistics.mean(r['g'] for r in rr), 'vermeld': sum(1 for r in rr if r['pos']) / len(rr),
                'gem_positie': statistics.mean(ps) if ps else None, 'eigen_domein_bron': sum(1 for r in rr if r['eigen']) / len(rr)}
    motoren = sorted({r['motor'] for r in S}); clusters = sorted({r['cluster'] for r in S})
    totaal = blok(S); per_motor = {m: blok([r for r in S if r['motor'] == m]) for m in motoren}
    per_cluster = {c: blok([r for r in S if r['cluster'] == c]) for c in clusters}

    vermeldingen = collections.Counter(); posities = collections.defaultdict(list)
    for r in S:
        for i, x in enumerate(r['merken'], 1): vermeldingen[x] += 1; posities[x].append(i)
    tot_verm = sum(vermeldingen.values())
    bronnen = collections.Counter(); bron_motor = collections.defaultdict(collections.Counter); bron_met_merk = collections.Counter()
    for r in S:
        for d in r['doms']:
            bronnen[d] += 1; bron_motor[d][r['motor']] += 1
            if r['pos']: bron_met_merk[d] += 1
    basis = totaal.get('vermeld', 0)

    # stabiliteit: zelfde prompt en motor, verschillende runs
    paren = collections.defaultdict(list)
    for r in S: paren[(r['pid'], r['motor'])].append(bool(r['pos']))
    meer = [v for v in paren.values() if len(v) > 1]; flips = sum(1 for v in meer if len(set(v)) > 1)

    F = Font(name='Arial', size=10); B = Font(name='Arial', size=10, bold=True); H = Font(name='Arial', size=10, bold=True, color='FFFFFF')
    fill = PatternFill('solid', fgColor='1F3A5F'); wrap = Alignment(wrap_text=True, vertical='top')
    def head(ws, cols, widths, row=1):
        for k, (c, w) in enumerate(zip(cols, widths)):
            x = ws.cell(row, k + 1, c); x.font = H; x.fill = fill; x.alignment = wrap; ws.column_dimensions[x.column_letter].width = w
        ws.freeze_panes = ws.cell(row + 1, 1)
    def pct(ws, cols, start=2):
        for row in ws.iter_rows(min_row=start):
            for x in row: x.font = F
            for c in cols:
                if c < len(row): row[c].number_format = '0%'

    wb = Workbook(); ws = wb.active; ws.title = 'Samenvatting'
    ws['A1'] = f'Nulmeting AI-zichtbaarheid: {merk}'; ws['A1'].font = Font(name='Arial', size=12, bold=True)
    ws['A2'] = f"Score = gemiddeld positiegewicht (plaats 1 = 1,0; 2-3 = 0,7; 4-5 = 0,4; lager = 0,2; afwezig = 0). Merkprompts tellen niet mee in de score. Antwoorden: {len(S)}."; ws['A2'].font = F
    head(ws, ['Niveau', 'Antwoorden', 'Score', 'Vermeld in', 'Gem. positie als vermeld', 'Eigen domein als bron'], [32, 12, 10, 12, 14, 14], 4)
    for naam, b in [('Totaal', totaal)] + [(f'Motor: {m}', b) for m, b in per_motor.items()] + [(f'Cluster: {c}', b) for c, b in per_cluster.items()]:
        ws.append([naam, b['antwoorden'], b.get('score'), b.get('vermeld'), round(b['gem_positie'], 1) if b.get('gem_positie') else '', b.get('eigen_domein_bron')])
    pct(ws, (2, 3, 5), 5)
    ws.append([]); ws.append(['Stabiliteit tussen runs', f'{flips} van {len(meer)} prompt-motorparen wisselen tussen vermeld en niet vermeld'])
    ws.cell(ws.max_row, 1).font = B

    w = wb.create_sheet('Per prompt')
    head(w, ['Prompt-id', 'Prompt', 'Cluster', 'Motor', 'Run', 'Datum', 'Websearch', 'Merkpositie', 'Gewicht', 'Merken in volgorde', 'Geciteerde domeinen', 'Eigen domein geciteerd', 'Merkprompt'], [9, 60, 18, 12, 6, 11, 10, 10, 8, 60, 60, 10, 10])
    for r in rijen:
        w.append([r['pid'], r['prompt'], r['cluster'], r['motor'], r['run'], r['datum'], r['ws'], r['pos'] or 'afwezig', r['g'], ' | '.join(r['merken']), ' | '.join(r['doms']), 'ja' if r['eigen'] else 'nee', 'ja' if r['merkprompt'] else ''])
    pct(w, ())
    w2 = wb.create_sheet('Merken')
    head(w2, ['Merk', 'Vermeldingen', 'Aandeel vermeldingen (share of voice)', 'Antwoorden met vermelding', 'Gem. positie', 'Is eigen merk'], [36, 12, 16, 14, 12, 10])
    for x, n in vermeldingen.most_common():
        w2.append([x, n, n / tot_verm if tot_verm else 0, n / len(S) if S else 0, round(statistics.mean(posities[x]), 1), 'ja' if x == merk else ''])
    pct(w2, (2, 3))
    w3 = wb.create_sheet('Bronnen')
    head(w3, ['Domein', 'Antwoorden met dit domein', 'Aandeel antwoorden', 'Merk vermeld als dit domein geciteerd is', 'Basiskans merk vermeld'] + motoren, [36, 12, 12, 16, 12] + [10] * len(motoren))
    for d, n in bronnen.most_common():
        w3.append([d, n, n / len(S) if S else 0, bron_met_merk[d] / n, basis] + [bron_motor[d][m] for m in motoren])
    pct(w3, (2, 3, 4))
    w4 = wb.create_sheet('Controles'); w4.column_dimensions['A'].width = 120
    w4.append(['Controles bij het scoren']); w4['A1'].font = B
    for f in fouten or ['Geen problemen gevonden.']: w4.append([f])
    w5 = wb.create_sheet('Toelichting'); w5.column_dimensions['A'].width = 26; w5.column_dimensions['B'].width = 110
    for k, v in [('Waargenomen', 'Antwoordtekst, merken in volgorde en geciteerde URLs per antwoord, zoals verzameld (connector, handmatig geplakt of export).'),
                 ('Afgeleid', 'Merkpositie (herberekend uit merken in volgorde met alle varianten), gewicht, score, share of voice en bronanalyse.'),
                 ('Score', 'Gemiddeld positiegewicht over alle antwoorden, zonder merkprompts.'),
                 ('Share of voice', 'Vermeldingen van een merk gedeeld door alle vermeldingen van aanbieders in dezelfde antwoorden.'),
                 ('Merk vermeld als dit domein geciteerd is', 'Vergelijk met de basiskans. Ligt het hoger, dan hangt die bron samen met vermelding. Samenhang, geen bewijs van oorzaak.'),
                 ('Beperking', 'AI-antwoorden variëren per run, per account, per locatie en per dag. Een nulmeting is een momentopname. Lees bij een herhaling alleen verschillen die groter zijn dan de wisseling tussen runs.')]:
        w5.append([k, v])
    for r in w5.iter_rows(): r[0].font = B; r[1].font = F; r[1].alignment = wrap
    wb.save(a.out)
    print(json.dumps({'antwoorden_gescoord': len(S), 'totaal': totaal, 'per_motor': per_motor, 'per_cluster': per_cluster,
                      'top_merken': vermeldingen.most_common(10), 'top_bronnen': bronnen.most_common(10),
                      'stabiliteit': f'{flips}/{len(meer)} paren wisselen', 'controles': fouten}, ensure_ascii=False, indent=1, default=str))


if __name__ == '__main__':
    main()

Installeren

Zo zet je hem in Claude

In Claude.ai of Claude Desktop

  1. Download ai-zichtbaarheid-nulmeting.zip. Pak hem niet uit.
  2. Ga naar Instellingen › Capabilities en zet code-uitvoering aan als dat nog niet zo is.
  3. Kies bij Skills voor Upload skill en selecteer de zip.
  4. Start een nieuw gesprek. Claude gebruikt de skill vanzelf zodra je vraag erbij past.

In Claude Code

  1. Download ai-zichtbaarheid-nulmeting.zip.
  2. Pak hem uit in je persoonlijke skillmap, of in .claude/skills/ van een project om hem met je team te delen:
unzip ai-zichtbaarheid-nulmeting.zip -d ~/.claude/skills/

Start Claude Code opnieuw. Met /skills zie je of hij geladen is.

Andere skills

Alle skills

Wil je dat we het samen met je team opzetten?

Plan een gesprek van 30 minuten met Mathias, de oprichter van Depends.

Plan een gesprek

info@depends.be+32 470 86 43 26

Bel onsPlan een kennismaking →