feat(#166): Berührungsgruppen-Aufschlüsselung im Stimmverhalten-Tab
Stimm-Index pro Beruehrungsgruppe (Matrix-Zeilen A-E) zusaetzlich zur bestehenden Werte-Aufschluesselung (Spalten 1-5). Toggle-Buttons in der 3. Sub-Section schalten zwischen Werte/Gruppen. - `aggregate_stimm_index_pro_gruppe()` analog zu `_pro_wert`, aber gruppiert nach `field[0]` (A-E) statt `field[-1]` (1-5). - `_gruppen_score_for_assessment()` Helper. - `GET /api/auswertungen/stimm-index-pro-gruppe`. - UI-Toggle "Pro GWÖ-Wert" / "Pro Berührungsgruppe" mit `setMatrixAxis()`. - 6 neue Tests, Suite jetzt 995 grün. Beruehrungsgruppen-Labels (aus app/models.py:MATRIX_LABELS gekuerzt): - A: Ausgelagerte Betriebe / Lieferant:innen - B: Finanzpartner:innen / Steuerzahler:innen - C: Politische Führung / Verwaltung / Ehrenamt - D: Bürger:innen und Wirtschaft - E: Staat, Gesellschaft und Natur Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d81753c4fb
commit
79003d6056
@ -271,6 +271,16 @@ GWOE_WERTE = {
|
|||||||
"5": "Transparenz & Demokratie",
|
"5": "Transparenz & Demokratie",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Zeilen der GWOe-Matrix: {field-prefix → Beruehrungsgruppe-Label}. Aus
|
||||||
|
# app/models.py:MATRIX_LABELS uebernommen, gekuerzt fuer UI-Spalten.
|
||||||
|
GWOE_BERUEHRUNGSGRUPPEN = {
|
||||||
|
"A": "Ausgelagerte Betriebe / Lieferant:innen",
|
||||||
|
"B": "Finanzpartner:innen / Steuerzahler:innen",
|
||||||
|
"C": "Politische Führung / Verwaltung / Ehrenamt",
|
||||||
|
"D": "Bürger:innen und Wirtschaft",
|
||||||
|
"E": "Staat, Gesellschaft und Natur",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _load_assessments_with_votes(
|
def _load_assessments_with_votes(
|
||||||
filter_bl: Optional[str] = None,
|
filter_bl: Optional[str] = None,
|
||||||
@ -518,6 +528,22 @@ def _wert_score_for_assessment(matrix: list[dict]) -> dict[str, float]:
|
|||||||
return {col: sum(vals) / len(vals) for col, vals in by_col.items()}
|
return {col: sum(vals) / len(vals) for col, vals in by_col.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def _gruppen_score_for_assessment(matrix: list[dict]) -> dict[str, float]:
|
||||||
|
"""Mittelwert pro Beruehrungsgruppe-Zeile (A..E). Field-Prefix ist die
|
||||||
|
Zeile (`field[0]`), z.B. A1..A5 alle in Gruppe A."""
|
||||||
|
by_row: defaultdict[str, list[float]] = defaultdict(list)
|
||||||
|
for entry in matrix or []:
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
continue
|
||||||
|
field = entry.get("field") or ""
|
||||||
|
rating = entry.get("rating")
|
||||||
|
if not field or rating is None:
|
||||||
|
continue
|
||||||
|
if len(field) >= 2 and field[0] in GWOE_BERUEHRUNGSGRUPPEN:
|
||||||
|
by_row[field[0]].append(float(rating))
|
||||||
|
return {row: sum(vals) / len(vals) for row, vals in by_row.items()}
|
||||||
|
|
||||||
|
|
||||||
def aggregate_stimm_index_pro_wert(
|
def aggregate_stimm_index_pro_wert(
|
||||||
filter_bl: Optional[str] = None,
|
filter_bl: Optional[str] = None,
|
||||||
filter_wp: Optional[str] = None,
|
filter_wp: Optional[str] = None,
|
||||||
@ -588,6 +614,74 @@ def aggregate_stimm_index_pro_wert(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def aggregate_stimm_index_pro_gruppe(
|
||||||
|
filter_bl: Optional[str] = None,
|
||||||
|
filter_wp: Optional[str] = None,
|
||||||
|
exclude_antragsteller: bool = True,
|
||||||
|
min_n: int = 5,
|
||||||
|
db_path: Optional[Path] = None,
|
||||||
|
) -> dict:
|
||||||
|
"""Pro (Fraktion, Beruehrungsgruppe A-E) ein Stimm-Index analog zu
|
||||||
|
pro_wert, aber gegen den Gruppen-Score statt den Wert-Score (#166).
|
||||||
|
|
||||||
|
Gruppen-Score eines Antrags = Ø(rating der gwoe_matrix-Felder mit
|
||||||
|
dem entsprechenden Zeilen-Prefix). Domain: -5..+5 pro Gruppe.
|
||||||
|
"""
|
||||||
|
rows = _load_assessments_with_votes(filter_bl, filter_wp, db_path)
|
||||||
|
|
||||||
|
gruppen_namen = list(GWOE_BERUEHRUNGSGRUPPEN.values())
|
||||||
|
gruppen_keys = list(GWOE_BERUEHRUNGSGRUPPEN.keys())
|
||||||
|
|
||||||
|
ja: defaultdict[tuple[str, str], list[float]] = defaultdict(list)
|
||||||
|
nein: defaultdict[tuple[str, str], list[float]] = defaultdict(list)
|
||||||
|
parteien_seen: set[str] = set()
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
gruppen_scores = _gruppen_score_for_assessment(row["gwoe_matrix"])
|
||||||
|
if not gruppen_scores:
|
||||||
|
continue
|
||||||
|
skip = row["antragsteller"] if exclude_antragsteller else set()
|
||||||
|
for f in row["ja"] - skip:
|
||||||
|
parteien_seen.add(f)
|
||||||
|
for row_key, sc in gruppen_scores.items():
|
||||||
|
ja[(f, row_key)].append(sc)
|
||||||
|
for f in row["nein"] - skip:
|
||||||
|
parteien_seen.add(f)
|
||||||
|
for row_key, sc in gruppen_scores.items():
|
||||||
|
nein[(f, row_key)].append(sc)
|
||||||
|
|
||||||
|
parteien = sorted(parteien_seen)
|
||||||
|
cells: dict[str, dict[str, dict]] = {}
|
||||||
|
for p in parteien:
|
||||||
|
cells[p] = {}
|
||||||
|
for row_key, gruppen_name in zip(gruppen_keys, gruppen_namen):
|
||||||
|
n_ja = len(ja[(p, row_key)])
|
||||||
|
n_nein = len(nein[(p, row_key)])
|
||||||
|
avg_ja = _avg(ja[(p, row_key)])
|
||||||
|
avg_nein = _avg(nein[(p, row_key)])
|
||||||
|
idx = (round(avg_ja - avg_nein, 2)
|
||||||
|
if avg_ja is not None and avg_nein is not None else None)
|
||||||
|
cells[p][gruppen_name] = {
|
||||||
|
"stimm_index": idx,
|
||||||
|
"n_ja": n_ja,
|
||||||
|
"n_nein": n_nein,
|
||||||
|
"ausreichend": n_ja >= min_n and n_nein >= min_n,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"fraktionen": parteien,
|
||||||
|
"gruppen": gruppen_namen,
|
||||||
|
"cells": cells,
|
||||||
|
"n_assessments_matched": len({r["drucksache"] for r in rows}),
|
||||||
|
"filter": {
|
||||||
|
"bundesland": filter_bl,
|
||||||
|
"wahlperiode": filter_wp,
|
||||||
|
"exclude_antragsteller": exclude_antragsteller,
|
||||||
|
"min_n": min_n,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def aggregate_empfehlungs_konsistenz(
|
def aggregate_empfehlungs_konsistenz(
|
||||||
filter_bl: Optional[str] = None,
|
filter_bl: Optional[str] = None,
|
||||||
filter_wp: Optional[str] = None,
|
filter_wp: Optional[str] = None,
|
||||||
|
|||||||
17
app/main.py
17
app/main.py
@ -2370,6 +2370,23 @@ async def auswertungen_stimm_index_cross_bl(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/auswertungen/stimm-index-pro-gruppe")
|
||||||
|
async def auswertungen_stimm_index_pro_gruppe(
|
||||||
|
bundesland: Optional[str] = None,
|
||||||
|
wahlperiode: Optional[str] = None,
|
||||||
|
exclude_antragsteller: bool = True,
|
||||||
|
min_n: int = 5,
|
||||||
|
):
|
||||||
|
"""Stimm-Index pro Beruehrungsgruppe (A-E der GWÖ-Matrix-Zeilen) (#166)."""
|
||||||
|
from .auswertungen import aggregate_stimm_index_pro_gruppe
|
||||||
|
return aggregate_stimm_index_pro_gruppe(
|
||||||
|
filter_bl=bundesland,
|
||||||
|
filter_wp=wahlperiode,
|
||||||
|
exclude_antragsteller=exclude_antragsteller,
|
||||||
|
min_n=min_n,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/auswertungen/empfehlungs-konsistenz")
|
@app.get("/api/auswertungen/empfehlungs-konsistenz")
|
||||||
async def auswertungen_empfehlungs_konsistenz(
|
async def auswertungen_empfehlungs_konsistenz(
|
||||||
bundesland: Optional[str] = None,
|
bundesland: Optional[str] = None,
|
||||||
|
|||||||
@ -262,15 +262,33 @@ table.modal-table th { background: var(--ecg-bg-subtle); font-weight: 700; }
|
|||||||
</div>
|
</div>
|
||||||
<div id="sv-heuchelei-meta" class="meta-line"></div>
|
<div id="sv-heuchelei-meta" class="meta-line"></div>
|
||||||
|
|
||||||
<!-- Sub 3: Pro GWÖ-Wert Heatmap -->
|
<!-- Sub 3: Pro GWÖ-Wert / Pro Berührungsgruppe (Toggle) -->
|
||||||
<h3 style="font-family:var(--font-display);font-size:15px;color:var(--ecg-teal);
|
<h3 style="font-family:var(--font-display);font-size:15px;color:var(--ecg-teal);
|
||||||
margin:1.5rem 0 0.5rem;">3. Stimm-Index pro GWÖ-Wert</h3>
|
margin:1.5rem 0 0.5rem;">3. Stimm-Index pro Matrix-Achse</h3>
|
||||||
<p style="font-size:11px;font-family:var(--font-mono);opacity:0.7;margin:0 0 0.5rem;">
|
<p style="font-size:11px;font-family:var(--font-mono);opacity:0.7;margin:0 0 0.5rem;">
|
||||||
Aufschlüsselung nach den fünf GWÖ-Werten (Würde, Solidarität,
|
Aufschlüsselung der Stimm-Indizes pro <strong>GWÖ-Wert</strong>
|
||||||
Nachhaltigkeit, Gerechtigkeit, Demokratie). Pro Zelle: Stimm-Index analog
|
(Spalten: Würde, Solidarität, Nachhaltigkeit, Gerechtigkeit,
|
||||||
Aussage 1, aber gegen den Wert-Score statt den Gesamt-Score. Domain
|
Demokratie) oder pro <strong>Berührungsgruppe</strong> (Zeilen A–E:
|
||||||
pro Zelle: −5..+5.
|
Lieferant:innen, Finanzpartner, Politik, Bürger, Staat). Pro Zelle:
|
||||||
|
Stimm-Index analog Aussage 1, aber gegen den Achsen-Score statt den
|
||||||
|
Gesamt-Score. Domain pro Zelle: −5..+5.
|
||||||
</p>
|
</p>
|
||||||
|
<div style="display:inline-flex;gap:4px;margin-bottom:0.5rem;">
|
||||||
|
<button type="button" id="sv-axis-werte" class="sv-axis-btn active"
|
||||||
|
onclick="setMatrixAxis('werte')"
|
||||||
|
style="font-family:var(--font-mono);font-size:11px;padding:5px 12px;
|
||||||
|
border:1px solid var(--ecg-border);border-radius:3px;
|
||||||
|
cursor:pointer;background:var(--ecg-teal);color:#fff;">
|
||||||
|
Pro GWÖ-Wert
|
||||||
|
</button>
|
||||||
|
<button type="button" id="sv-axis-gruppen" class="sv-axis-btn"
|
||||||
|
onclick="setMatrixAxis('gruppen')"
|
||||||
|
style="font-family:var(--font-mono);font-size:11px;padding:5px 12px;
|
||||||
|
border:1px solid var(--ecg-border);border-radius:3px;
|
||||||
|
cursor:pointer;background:var(--ecg-card-bg);color:var(--ecg-dark);">
|
||||||
|
Pro Berührungsgruppe
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="sv-wert-heatmap" class="matrix-wrap"></div>
|
<div id="sv-wert-heatmap" class="matrix-wrap"></div>
|
||||||
|
|
||||||
<!-- Sub 4: Empfehlungs-Konsistenz Bar Chart -->
|
<!-- Sub 4: Empfehlungs-Konsistenz Bar Chart -->
|
||||||
@ -334,6 +352,36 @@ table.modal-table th { background: var(--ecg-bg-subtle); font-weight: 700; }
|
|||||||
<script>
|
<script>
|
||||||
let _tabLoaded = { 'bl-partei': false, 'themen': false, 'stimmverhalten': false };
|
let _tabLoaded = { 'bl-partei': false, 'themen': false, 'stimmverhalten': false };
|
||||||
let _svCharts = { index: null, heuchelei: null, empfehlung: null, crossBl: null };
|
let _svCharts = { index: null, heuchelei: null, empfehlung: null, crossBl: null };
|
||||||
|
let _svMatrixAxis = 'werte'; // 'werte' or 'gruppen'
|
||||||
|
|
||||||
|
function setMatrixAxis(axis) {
|
||||||
|
_svMatrixAxis = axis;
|
||||||
|
const werteBtn = document.getElementById('sv-axis-werte');
|
||||||
|
const gruppenBtn = document.getElementById('sv-axis-gruppen');
|
||||||
|
if (axis === 'werte') {
|
||||||
|
werteBtn.style.background = 'var(--ecg-teal)';
|
||||||
|
werteBtn.style.color = '#fff';
|
||||||
|
gruppenBtn.style.background = 'var(--ecg-card-bg)';
|
||||||
|
gruppenBtn.style.color = 'var(--ecg-dark)';
|
||||||
|
} else {
|
||||||
|
gruppenBtn.style.background = 'var(--ecg-teal)';
|
||||||
|
gruppenBtn.style.color = '#fff';
|
||||||
|
werteBtn.style.background = 'var(--ecg-card-bg)';
|
||||||
|
werteBtn.style.color = 'var(--ecg-dark)';
|
||||||
|
}
|
||||||
|
loadMatrixHeatmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMatrixHeatmap() {
|
||||||
|
const blRaw = (window.v2GetGlobalBl && window.v2GetGlobalBl()) || 'ALL';
|
||||||
|
const bl = (blRaw === 'ALL') ? '' : blRaw;
|
||||||
|
const exclude = document.getElementById('sv-exclude-antragsteller').checked ? '1' : '0';
|
||||||
|
if (_svMatrixAxis === 'gruppen') {
|
||||||
|
loadStimmIndexProGruppe(bl, exclude);
|
||||||
|
} else {
|
||||||
|
loadStimmIndexProWert(bl, exclude);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function switchTab(id, btn) {
|
function switchTab(id, btn) {
|
||||||
document.querySelectorAll('.auswert-tab').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.auswert-tab').forEach(b => b.classList.remove('active'));
|
||||||
@ -539,7 +587,7 @@ async function loadStimmverhalten() {
|
|||||||
|
|
||||||
loadStimmIndex(bl, exclude);
|
loadStimmIndex(bl, exclude);
|
||||||
loadHeuchelei(bl);
|
loadHeuchelei(bl);
|
||||||
loadStimmIndexProWert(bl, exclude);
|
loadMatrixHeatmap();
|
||||||
loadEmpfehlungsKonsistenz(bl);
|
loadEmpfehlungsKonsistenz(bl);
|
||||||
loadStimmIndexCrossBl(exclude);
|
loadStimmIndexCrossBl(exclude);
|
||||||
}
|
}
|
||||||
@ -739,6 +787,51 @@ async function loadStimmIndexProWert(bl, exclude) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadStimmIndexProGruppe(bl, exclude) {
|
||||||
|
const wrap = document.getElementById('sv-wert-heatmap');
|
||||||
|
let url = `/api/auswertungen/stimm-index-pro-gruppe?exclude_antragsteller=${exclude}&min_n=3`;
|
||||||
|
if (bl) url += '&bundesland=' + encodeURIComponent(bl);
|
||||||
|
|
||||||
|
wrap.innerHTML = '<div style="font-family:var(--font-mono);font-size:12px;opacity:0.5;">Lade Heatmap …</div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await fetch(url);
|
||||||
|
const data = await r.json();
|
||||||
|
|
||||||
|
if (!data.fraktionen.length) {
|
||||||
|
wrap.innerHTML = '<div style="font-family:var(--font-mono);font-size:12px;opacity:0.5;">Keine Daten für diesen Filter.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '<table class="gwoe-matrix"><thead><tr><th class="row-h">Fraktion</th>';
|
||||||
|
for (const g of data.gruppen) html += `<th style="font-size:10px;max-width:120px;white-space:normal;">${g}</th>`;
|
||||||
|
html += '</tr></thead><tbody>';
|
||||||
|
|
||||||
|
for (const partei of data.fraktionen) {
|
||||||
|
html += `<tr><th class="row-h">${partei}</th>`;
|
||||||
|
for (const gruppe of data.gruppen) {
|
||||||
|
const cell = (data.cells[partei] || {})[gruppe];
|
||||||
|
if (cell && cell.stimm_index != null && cell.ausreichend) {
|
||||||
|
const color = wertHeatColor(cell.stimm_index);
|
||||||
|
const sign = cell.stimm_index >= 0 ? '+' : '';
|
||||||
|
html += `<td style="background:${color};" title="${partei} × ${gruppe}: Index ${sign}${cell.stimm_index} (n_ja=${cell.n_ja}, n_nein=${cell.n_nein})">
|
||||||
|
${sign}${cell.stimm_index.toFixed(1)}
|
||||||
|
</td>`;
|
||||||
|
} else {
|
||||||
|
html += '<td class="empty">—</td>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html += '</tr>';
|
||||||
|
}
|
||||||
|
html += '</tbody></table>';
|
||||||
|
html += `<div class="meta-line">Datenbasis: ${data.n_assessments_matched} Anträge.
|
||||||
|
Mindest-N pro Zelle: 3. Skala −5..+5 (Gruppen-Score-Differenz Ja minus Nein).</div>`;
|
||||||
|
wrap.innerHTML = html;
|
||||||
|
} catch (e) {
|
||||||
|
wrap.innerHTML = `<div style="color:#c00;font-family:var(--font-mono);font-size:12px;">Fehler: ${e}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadEmpfehlungsKonsistenz(bl) {
|
async function loadEmpfehlungsKonsistenz(bl) {
|
||||||
const meta = document.getElementById('sv-empfehlung-meta');
|
const meta = document.getElementById('sv-empfehlung-meta');
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,10 @@ from app.auswertungen import (
|
|||||||
aggregate_heuchelei,
|
aggregate_heuchelei,
|
||||||
aggregate_stimm_index,
|
aggregate_stimm_index,
|
||||||
aggregate_stimm_index_cross_bl,
|
aggregate_stimm_index_cross_bl,
|
||||||
|
aggregate_stimm_index_pro_gruppe,
|
||||||
aggregate_stimm_index_pro_wert,
|
aggregate_stimm_index_pro_wert,
|
||||||
export_stimmverhalten_csv,
|
export_stimmverhalten_csv,
|
||||||
|
_gruppen_score_for_assessment,
|
||||||
_wert_score_for_assessment,
|
_wert_score_for_assessment,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -377,6 +379,65 @@ class TestAggregateProWert:
|
|||||||
assert "ausreichend" in cell
|
assert "ausreichend" in cell
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# aggregate_stimm_index_pro_gruppe (#166)
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
class TestGruppenScoreHelper:
|
||||||
|
def test_extracts_per_row(self):
|
||||||
|
matrix = [
|
||||||
|
{"field": "A1", "rating": 3},
|
||||||
|
{"field": "A3", "rating": 5},
|
||||||
|
{"field": "B2", "rating": 4},
|
||||||
|
]
|
||||||
|
result = _gruppen_score_for_assessment(matrix)
|
||||||
|
# Zeile A: A1=3 + A3=5 → Ø=4
|
||||||
|
assert result["A"] == 4.0
|
||||||
|
# Zeile B: B2=4 → Ø=4
|
||||||
|
assert result["B"] == 4.0
|
||||||
|
|
||||||
|
def test_empty_matrix(self):
|
||||||
|
assert _gruppen_score_for_assessment([]) == {}
|
||||||
|
|
||||||
|
def test_invalid_entries_skipped(self):
|
||||||
|
matrix = [
|
||||||
|
{"field": "A1", "rating": 3},
|
||||||
|
{"field": "X9", "rating": 2}, # invalid row prefix
|
||||||
|
{"field": "", "rating": 5},
|
||||||
|
]
|
||||||
|
result = _gruppen_score_for_assessment(matrix)
|
||||||
|
assert result == {"A": 3.0}
|
||||||
|
|
||||||
|
|
||||||
|
class TestAggregateProGruppe:
|
||||||
|
def test_structure(self, sample_db):
|
||||||
|
out = aggregate_stimm_index_pro_gruppe(db_path=sample_db, min_n=1)
|
||||||
|
# 5 Berührungsgruppen-Labels
|
||||||
|
assert len(out["gruppen"]) == 5
|
||||||
|
assert "cells" in out
|
||||||
|
|
||||||
|
def test_cell_format(self, sample_db):
|
||||||
|
out = aggregate_stimm_index_pro_gruppe(db_path=sample_db, min_n=1)
|
||||||
|
if not out["fraktionen"]:
|
||||||
|
pytest.skip("no parteien — DB empty")
|
||||||
|
first_partei = out["fraktionen"][0]
|
||||||
|
# Pick first gruppe
|
||||||
|
first_gruppe = out["gruppen"][0]
|
||||||
|
cell = out["cells"][first_partei][first_gruppe]
|
||||||
|
assert "stimm_index" in cell
|
||||||
|
assert "n_ja" in cell
|
||||||
|
assert "n_nein" in cell
|
||||||
|
assert "ausreichend" in cell
|
||||||
|
|
||||||
|
def test_independent_from_pro_wert(self, sample_db):
|
||||||
|
"""Pro-Gruppe-Aggregation soll andere Achse als Pro-Wert haben."""
|
||||||
|
wert_out = aggregate_stimm_index_pro_wert(db_path=sample_db, min_n=1)
|
||||||
|
gruppe_out = aggregate_stimm_index_pro_gruppe(db_path=sample_db, min_n=1)
|
||||||
|
# Werte und Gruppen-Labels müssen disjunkt sein
|
||||||
|
assert set(wert_out["werte"]).isdisjoint(set(gruppe_out["gruppen"]))
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# aggregate_stimm_index_cross_bl
|
# aggregate_stimm_index_cross_bl
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user