Labels: feature-request, AI, data-enrichment
name: Feature Request — AI-gestützte Konzernzugehörigkeit
about: Neue Funktion zur automatischen Erkennung und Anzeige von Konzernstrukturen
title: "[FEATURE] AI-gestützte Konzernzugehörigkeit für Zefix-Unternehmen"
labels: feature-request, AI, data-enrichment, companies-view
assignees: ''
💡 Feature Summary
Es soll eine neue Funktion entwickelt werden, die mithilfe von AI automatisch die Konzernzugehörigkeit von Schweizer Unternehmen aus dem Zefix erkennt und zuordnet. In der Unternehmensansicht soll ein neues Filterfeld „Konzernzugehörigkeit" erscheinen, das es ermöglicht, alle Gesellschaften eines Konzerns gesammelt anzuzeigen — z.B. werden A AG, B AG und C AG dem A Konzern zugeordnet.
Motivation / Use Case
Viele Schweizer Konzerne bestehen aus Dutzenden oder Hunderten von rechtlich eigenständigen Gesellschaften, die im Zefix als separate Einträge erscheinen. Für Prüfer, Compliance-Teams und Analysten ist es heute sehr aufwändig, alle Gesellschaften eines Konzerns manuell zu identifizieren.
Beispiel:
Nestlé SA, Nestlé Suisse SA, Nestlé Products SA, Nestlé Research AG → alle gehören zum Nestlé Konzern
Mit dieser Funktion können Nutzer:
- Alle Konzerngesellschaften auf einen Blick sehen
- Konzernweite Risiken, Revisionsstellen und Verwaltungsräte analysieren
- Due-Diligence-Prüfungen konzernweit durchführen
Gewünschtes Verhalten (User Story)
Als Wirtschaftsprüfer / Compliance-Analyst
möchte ich bei der Unternehmenssuche nach Konzernzugehörigkeit filtern können
damit ich alle Gesellschaften eines Konzerns vollständig und effizient analysieren kann.
UI — Neue Filteroption „Konzernzugehörigkeit"
Unternehmen
├── Filter: Kanton [Luzern ▼]
├── Filter: Rechtsform [AG ▼]
├── Filter: Revisionsstelle [PwC ▼]
└── Filter: Konzernzugehörigkeit [Nestlé Konzern ▼] ← NEU
Ergebnisse (4 Unternehmen):
┌──────────────────────────────────────────────────────┐
│ Nestlé SA CHE-100.349.897 Aktiv │
│ Nestlé Suisse SA CHE-106.212.193 Aktiv │
│ Nestlé Products SA CHE-102.933.012 Aktiv │
│ Nestlé Research AG CHE-108.442.671 Aktiv │
└──────────────────────────────────────────────────────┘
Technische Lösung
Übersicht der Architektur
Zefix Rohdaten
│
▼
[1] Signale extrahieren
(Namensähnlichkeit, gemeinsame VR-Mitglieder,
Adresse, Beteiligungsstruktur)
│
▼
[2] AI-Clustering (LLM + Graph-Algorithmus)
│
▼
[3] Konzern-Tabelle befüllen
(konzern_name, confidence_score, methode)
│
▼
[4] API-Endpoint + UI-Filter bereitstellen
Schritt 1 — Datenbank-Schema erweitern
-- Neue Tabelle: Konzerne
CREATE TABLE corporate_groups (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL, -- z.B. "Nestlé Konzern"
parent_uid TEXT REFERENCES companies(uid), -- Muttergesellschaft
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
source TEXT DEFAULT 'ai', -- 'ai' | 'manual' | 'ownership'
confidence FLOAT DEFAULT 0.0 -- 0.0 – 1.0
);
-- Neue Spalte in companies-Tabelle
ALTER TABLE companies
ADD COLUMN group_id UUID REFERENCES corporate_groups(id),
ADD COLUMN group_confidence FLOAT DEFAULT NULL;
-- Index für schnelle Filter-Abfragen
CREATE INDEX idx_companies_group_id ON companies(group_id);
Schritt 2 — AI-Clustering-Pipeline
Die Pipeline läuft täglich (via Cron-Job) und kombiniert drei Methoden:
Methode A: Namensähnlichkeit (Heuristik)
import re
from difflib import SequenceMatcher
def extract_group_name_candidate(company_name: str) -> str:
"""
Extrahiert den Kernnahmen aus einem Firmennamen.
'Nestlé Suisse SA' → 'Nestlé'
'PricewaterhouseCoopers AG' → 'PricewaterhouseCoopers'
"""
# Entferne Rechtsformen
suffixes = r'\b(AG|SA|GmbH|Sàrl|Ltd|KG|OHG|Genossenschaft|Stiftung)\b'
cleaned = re.sub(suffixes, '', company_name, flags=re.IGNORECASE).strip()
# Erstes Wort als Kandidat
return cleaned.split()[0] if cleaned else company_name
def similarity(a: str, b: str) -> float:
return SequenceMatcher(None, a.lower(), b.lower()).ratio()
Methode B: Gemeinsame Verwaltungsratsmitglieder (Graph)
import networkx as nx
def build_board_overlap_graph(companies: list[dict]) -> nx.Graph:
"""
Verbindet Unternehmen, die ≥1 gemeinsames VR-Mitglied haben.
Starke Verbindungen (≥2 gemeinsame Personen) = wahrscheinlich Konzern.
"""
G = nx.Graph()
person_to_companies = {} # person_uid → [company_uid, ...]
for company in companies:
for person in company.get('board_members', []):
pid = person['uid']
if pid not in person_to_companies:
person_to_companies[pid] = []
person_to_companies[pid].append(company['uid'])
for person_uid, company_uids in person_to_companies.items():
for i in range(len(company_uids)):
for j in range(i + 1, len(company_uids)):
u, v = company_uids[i], company_uids[j]
if G.has_edge(u, v):
G[u][v]['weight'] += 1
else:
G.add_edge(u, v, weight=1)
return G
Methode C: AI-Klassifizierung via LLM (Claude API)
import anthropic
client = anthropic.Anthropic()
def classify_group_with_ai(company_names: list[str]) -> dict:
"""
Nutzt Claude, um Firmennamen einem Konzern zuzuordnen.
"""
prompt = f"""
Du bist ein Experte für Schweizer Unternehmensstrukturen.
Analysiere diese Firmennamen aus dem Zefix und gruppiere sie nach Konzernzugehörigkeit.
Antworte NUR mit validem JSON. Format:
{{
"groups": [
{{
"konzern_name": "Name des Konzerns",
"parent_company": "Name der Muttergesellschaft oder null",
"members": ["Firma A", "Firma B"],
"confidence": 0.95,
"reasoning": "Kurze Begründung"
}}
]
}}
Firmennamen:
{chr(10).join(f'- {name}' for name in company_names)}
"""
message = client.messages.create(
model="claude-opus-4-6",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}]
)
import json
return json.loads(message.content[0].text)
Schritt 3 — Neuer API-Endpoint
// GET /v1/groups — alle Konzerne auflisten
// GET /v1/groups/{id} — Konzerndetails + alle Mitglieder
// GET /v1/groups/{id}/companies — alle Gesellschaften eines Konzerns
// POST /v1/groups/{id}/merge — zwei Konzerne manuell zusammenführen
// PUT /v1/companies/{uid}/group — manuelle Zuweisung
// Erweiterung des bestehenden /v1/companies Endpoints:
// GET /v1/companies?group_id=<uuid> ← neuer Filter-Parameter
/**
Beispiel-Response für GET /v1/groups/abc-123
*/
{
"id": "abc-123",
"name": "Nestlé Konzern",
"parent_uid": "CHE-100.349.897",
"parent_name": "Nestlé SA",
"member_count": 47,
"confidence": 0.94,
"source": "ai",
"companies": [
{
"uid": "CHE-100.349.897",
"name": "Nestlé SA",
"canton": "VD",
"status": "Active",
"is_parent": true
},
{
"uid": "CHE-106.212.193",
"name": "Nestlé Suisse SA",
"canton": "VD",
"status": "Active",
"is_parent": false
}
// ...
]
}
Schritt 4 — UI-Anpassung (Companies-Seite)
// Neues Filter-Feld in der Companies-Ansicht
// 1. Autocomplete-Suche für Konzernname
<FilterField
label="Konzernzugehörigkeit"
placeholder="z.B. Nestlé, Zurich Insurance, UBS..."
endpoint="/api/v1/groups?search={query}"
onSelect={(group) => applyFilter('group_id', group.id)}
/>
// 2. Anzeige in der Unternehmenstabelle
<TableColumn
header="Konzern"
render={(company) =>
company.group ? (
<Badge
label={company.group.name}
onClick={() => applyFilter('group_id', company.group.id)}
confidence={company.group_confidence}
/>
) : (
<span className="text-muted">—</span>
)
}
/>
// 3. Konzernübersicht-Karte beim Klick auf einen Konzern
<GroupDetailPanel group={selectedGroup}>
<GroupHierarchyTree /> // Visualisierung der Konzernstruktur
<GroupCompanyTable /> // Tabelle aller Gesellschaften
<GroupRiskSummary /> // Aggregierter Risk Score
<GroupAuditorOverview /> // Revisionsstellen im Konzern
</GroupDetailPanel>
Schritt 5 — Cron-Job für tägliche Aktualisierung
# Läuft täglich nach dem Zefix-Sync
# Pseudocode für die Pipeline
async def update_corporate_groups():
# 1. Alle Unternehmen laden
companies = await db.fetch("SELECT uid, name, board_members FROM companies")
# 2. Graph-Methode: Board-Overlap
G = build_board_overlap_graph(companies)
graph_clusters = detect_communities(G, min_weight=2)
# 3. Namens-Methode: Gruppierung nach Kernname
name_clusters = cluster_by_name_similarity(companies, threshold=0.75)
# 4. Bestehende Ownership-Daten aus /v1/relationships einbeziehen
ownership_groups = await fetch_ownership_clusters()
# 5. Alle Signale zusammenführen
merged = merge_cluster_signals(graph_clusters, name_clusters, ownership_groups)
# 6. AI-Validierung für unsichere Cluster (confidence < 0.8)
uncertain = [c for c in merged if c.confidence < 0.8]
if uncertain:
ai_results = await classify_group_with_ai([c.names for c in uncertain])
apply_ai_corrections(merged, ai_results)
# 7. Datenbank aktualisieren
await upsert_groups(merged)
print(f"✅ {len(merged)} Konzerne aktualisiert")
Credit-Kosten (Vorschlag)
| Operation |
Credits |
| GET /v1/groups (Liste) |
1 |
| GET /v1/groups/{id} (Detail) |
2 |
| GET /v1/companies?group_id=... |
1 |
| POST /v1/ai/group-classify (manuelle AI-Anfrage) |
10 |
Akzeptanzkriterien
Zusätzliche Hinweise
- VynCo nutzt bereits
/v1/companies/{uid}/relationships und /v1/companies/{uid}/hierarchy — diese Daten sollten als erstes Signal genutzt werden, bevor AI eingesetzt wird
- Der Confidence-Score erlaubt es Nutzern, die Zuverlässigkeit einer Zuordnung einzuschätzen
- Eine manuelle Override-Funktion ist wichtig, damit Nutzer fehlerhafte AI-Zuordnungen korrigieren können
- Langfristig könnte dies zu einem eigenen „Konzernanalyse"-Dashboard ausgebaut werden
Labels: feature-request, AI, data-enrichment
name: Feature Request — AI-gestützte Konzernzugehörigkeit about: Neue Funktion zur automatischen Erkennung und Anzeige von Konzernstrukturen title: "[FEATURE] AI-gestützte Konzernzugehörigkeit für Zefix-Unternehmen" labels: feature-request, AI, data-enrichment, companies-view assignees: ''
💡 Feature Summary
Es soll eine neue Funktion entwickelt werden, die mithilfe von AI automatisch die Konzernzugehörigkeit von Schweizer Unternehmen aus dem Zefix erkennt und zuordnet. In der Unternehmensansicht soll ein neues Filterfeld „Konzernzugehörigkeit" erscheinen, das es ermöglicht, alle Gesellschaften eines Konzerns gesammelt anzuzeigen — z.B. werden
A AG,B AGundC AGdem A Konzern zugeordnet.Motivation / Use Case
Viele Schweizer Konzerne bestehen aus Dutzenden oder Hunderten von rechtlich eigenständigen Gesellschaften, die im Zefix als separate Einträge erscheinen. Für Prüfer, Compliance-Teams und Analysten ist es heute sehr aufwändig, alle Gesellschaften eines Konzerns manuell zu identifizieren.
Beispiel:
Mit dieser Funktion können Nutzer:
Gewünschtes Verhalten (User Story)
UI — Neue Filteroption „Konzernzugehörigkeit"
Technische Lösung
Übersicht der Architektur
Schritt 1 — Datenbank-Schema erweitern
Schritt 2 — AI-Clustering-Pipeline
Die Pipeline läuft täglich (via Cron-Job) und kombiniert drei Methoden:
Methode A: Namensähnlichkeit (Heuristik)
Methode B: Gemeinsame Verwaltungsratsmitglieder (Graph)
Methode C: AI-Klassifizierung via LLM (Claude API)
Schritt 3 — Neuer API-Endpoint
Beispiel-Response für GET /v1/groups/abc-123
*/
{
"id": "abc-123",
"name": "Nestlé Konzern",
"parent_uid": "CHE-100.349.897",
"parent_name": "Nestlé SA",
"member_count": 47,
"confidence": 0.94,
"source": "ai",
"companies": [
{
"uid": "CHE-100.349.897",
"name": "Nestlé SA",
"canton": "VD",
"status": "Active",
"is_parent": true
},
{
"uid": "CHE-106.212.193",
"name": "Nestlé Suisse SA",
"canton": "VD",
"status": "Active",
"is_parent": false
}
// ...
]
}
Schritt 4 — UI-Anpassung (Companies-Seite)
Schritt 5 — Cron-Job für tägliche Aktualisierung
Credit-Kosten (Vorschlag)
Akzeptanzkriterien
corporate_groupsin der DatenbankGET /v1/companies?group_id=<uuid>gibt korrekte ErgebnisseGET /v1/groups/{id}gibt alle Mitglieder mit Confidence-Score zurückZusätzliche Hinweise
/v1/companies/{uid}/relationshipsund/v1/companies/{uid}/hierarchy— diese Daten sollten als erstes Signal genutzt werden, bevor AI eingesetzt wird