generated from coulomb/repo-seed
Implementiert UC-MB-01 bis UC-MB-03: Marktbegleiter-Katalog (Liste, Detail, Anlegen/Bearbeiten), Ausschreibungspassagen mit Verlässlichkeitsscore (1–10, Validator), Musterauswertung mit Aggregationen (Ausschreiber-Häufigkeit, Ø-Score, Passagen-Anzahl). 4 Tests grün. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
132 lines
4.5 KiB
Markdown
132 lines
4.5 KiB
Markdown
---
|
|
id: WP-0011
|
|
title: Marktbegleiter-Analyse
|
|
status: done
|
|
phase: 11-of-12
|
|
created: "2026-05-08"
|
|
depends_on: WP-0010
|
|
---
|
|
|
|
# WP-0011 — Marktbegleiter-Analyse
|
|
|
|
Marktbegleiter-Katalog, Passagen-Erfassung mit Verlässlichkeitsscore,
|
|
Musterauswertung. Referenz: UC-MB-01 bis UC-MB-03.
|
|
|
|
---
|
|
|
|
```task
|
|
id: WP-0011-T01
|
|
title: Marktbegleiter-Katalog: Liste und Anlegen (UC-MB-01)
|
|
status: done
|
|
|
|
`marktbegleiter/views.py` — marktbegleiter_liste, marktbegleiter_neu/_bearbeiten:
|
|
|
|
Liste: Tabellenansicht mit Name, Branchen (gekürzt), Aktualisierungsdatum.
|
|
Filter: Branche (Freitext-Match auf `relevante_branchen`).
|
|
|
|
`MarktbegleiterForm(ModelForm)`:
|
|
`typische_formulierungen` als Textarea mit Placeholder "Eine Formulierung pro Zeile".
|
|
`vertraulichkeit` als Select (intern/streng_vertraulich).
|
|
|
|
marktbegleiter_detail:
|
|
- Profil-Abschnitte: Portfolio, Stärken/Schwächen, typische Formulierungen (als Tags/Pilllen)
|
|
- Abschnitt "Verknüpfte Passagen": Liste aller Ausschreibungspassagen, sortiert nach Verlässlichkeit desc
|
|
- Aggregation: "Erscheint in X Ausschreibungen, Ø Verlässlichkeit: Y"
|
|
- CustomAttribute-Panel
|
|
```
|
|
|
|
```task
|
|
id: WP-0011-T02
|
|
title: Ausschreibungspassage erfassen (UC-MB-02, UC-MB-03)
|
|
status: done
|
|
|
|
`marktbegleiter/passagen_views.py` — passagen_liste und passage_neu:
|
|
|
|
passagen_liste (pro Ausschreibung):
|
|
`Ausschreibungspassage.objects.filter(ausschreibung_id=ausschreibung_id).select_related('marktbegleiter', 'dokument')`
|
|
Template `marktbegleiter/passagen_liste.html`: Tabelle mit Textauszug (150 Zeichen), Marktbegleiter,
|
|
Score, Kategorie, Datum.
|
|
|
|
`AusschreibungspassageForm(ModelForm)`:
|
|
- `passage` als großes Textarea
|
|
- `verlaesslichkeitsscore` als Range-Input 1-10 (mit Alpine.js-Beschriftung: "1=sehr unsicher, 10=sehr sicher")
|
|
- `marktbegleiter` als Select + Link "Neuen Marktbegleiter anlegen" (öffnet Modal)
|
|
- `dokument` als Select (nur Dokumente dieser Ausschreibung)
|
|
- `kategorie` als Select: formulierung/leistungsmerkmal/zertifizierung/referenz/sonstiges
|
|
|
|
Nach Speichern: Passage erscheint auf Ausschreibungsdetail (Tab "Marktbegleiter")
|
|
und im Marktbegleiter-Profil.
|
|
```
|
|
|
|
```task
|
|
id: WP-0011-T03
|
|
title: Marktbegleiter-Musterauswertung (UC-MB-03)
|
|
status: done
|
|
|
|
`marktbegleiter/views.py` — marktbegleiter_auswertung:
|
|
|
|
```python
|
|
def marktbegleiter_auswertung(request, pk):
|
|
mb = get_object_or_404(Marktbegleiter, pk=pk)
|
|
passagen = Ausschreibungspassage.objects.filter(marktbegleiter=mb).select_related(
|
|
'ausschreibung', 'dokument'
|
|
)
|
|
# Aggregationen
|
|
ausschreiber_haeufigkeit = passagen.values(
|
|
'ausschreibung__ausschreiber'
|
|
).annotate(count=Count('id')).order_by('-count')[:10]
|
|
|
|
score_durchschnitt = passagen.aggregate(Avg('verlaesslichkeitsscore'))
|
|
|
|
ctx = {
|
|
'marktbegleiter': mb,
|
|
'passagen': passagen,
|
|
'ausschreiber_haeufigkeit': ausschreiber_haeufigkeit,
|
|
'score_durchschnitt': score_durchschnitt['verlaesslichkeitsscore__avg'],
|
|
'anzahl_ausschreibungen': passagen.values('ausschreibung').distinct().count(),
|
|
}
|
|
return render(request, 'marktbegleiter/auswertung.html', ctx)
|
|
```
|
|
|
|
Template `marktbegleiter/auswertung.html`:
|
|
- Statistikkacheln: Anzahl Passagen, Anzahl Ausschreibungen, Ø Score
|
|
- Tabelle: Häufigste Ausschreiber
|
|
- Alle Passagen sortierbar nach Datum / Score / Ausschreibung
|
|
```
|
|
|
|
```task
|
|
id: WP-0011-T04
|
|
title: URL-Verkabelung und Tests
|
|
status: done
|
|
|
|
`marktbegleiter/passagen_urls.py`:
|
|
```python
|
|
urlpatterns = [
|
|
path('', passagen_views.passagen_liste, name='liste'),
|
|
path('neu/', passagen_views.passage_neu, name='neu'),
|
|
path('<int:pk>/', passagen_views.passage_detail, name='detail'),
|
|
path('<int:pk>/bearbeiten/', passagen_views.passage_bearbeiten, name='bearbeiten'),
|
|
]
|
|
```
|
|
|
|
`marktbegleiter/urls.py`:
|
|
```python
|
|
app_name = 'marktbegleiter'
|
|
urlpatterns = [
|
|
path('', views.marktbegleiter_liste, name='liste'),
|
|
path('neu/', views.marktbegleiter_neu, name='neu'),
|
|
path('<int:pk>/', views.marktbegleiter_detail, name='detail'),
|
|
path('<int:pk>/auswertung/', views.marktbegleiter_auswertung, name='auswertung'),
|
|
path('<int:pk>/bearbeiten/', views.marktbegleiter_bearbeiten, name='bearbeiten'),
|
|
]
|
|
```
|
|
|
|
Global in Haupt-URLs: `path('marktbegleiter/', include('vergabe_teilnahme.apps.marktbegleiter.urls'))`
|
|
|
|
Tests:
|
|
- Test: Passage anlegen mit verlaesslichkeitsscore=10 → gespeichert
|
|
- Test: verlaesslichkeitsscore=11 → ValidationError (MinValueValidator/MaxValueValidator)
|
|
- Test: Auswertungs-View mit Passagen → score_durchschnitt korrekt berechnet
|
|
- Test: Marktbegleiter-Detail zeigt Passagen-Liste
|
|
```
|