Files
vergabe-teilnahme/vergabe_teilnahme/apps/marktbegleiter/views.py
tegwick bde10f3a69 feat(WP-0011): Marktbegleiter-Analyse — Katalog, Passagen, Auswertung
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>
2026-05-11 16:20:55 +02:00

114 lines
5.3 KiB
Python

from django.db.models import Avg, Count
from django import forms
from django.shortcuts import get_object_or_404, redirect, render
from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung
from .models import Ausschreibungspassage, Marktbegleiter
# ── Forms ─────────────────────────────────────────────────────────────────────
class MarktbegleiterForm(forms.ModelForm):
class Meta:
model = Marktbegleiter
fields = [
'name', 'kurzbeschreibung', 'produkt_leistungsportfolio',
'relevante_branchen', 'bekannte_staerken', 'bekannte_schwaechen',
'typische_formulierungen', 'typische_leistungsmerkmale',
'bekannte_zertifizierungen', 'bekannte_referenzen',
'quellen_links', 'letzte_aktualisierung', 'interne_notizen',
'vertraulichkeit',
]
widgets = {
'name': forms.TextInput(attrs={'class': 'form-input'}),
'kurzbeschreibung': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
'produkt_leistungsportfolio': forms.Textarea(attrs={'class': 'form-input', 'rows': 4}),
'relevante_branchen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
'bekannte_staerken': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
'bekannte_schwaechen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
'typische_formulierungen': forms.Textarea(attrs={
'class': 'form-input', 'rows': 5,
'placeholder': 'Eine Formulierung pro Zeile',
}),
'typische_leistungsmerkmale': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
'bekannte_zertifizierungen': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}),
'bekannte_referenzen': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}),
'quellen_links': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}),
'letzte_aktualisierung': forms.DateInput(attrs={'type': 'date', 'class': 'form-input'}),
'interne_notizen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
'vertraulichkeit': forms.Select(attrs={'class': 'form-select'}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['letzte_aktualisierung'].required = False
# ── Views ─────────────────────────────────────────────────────────────────────
def marktbegleiter_liste(request):
qs = Marktbegleiter.objects.all()
q = request.GET.get('q', '')
branche = request.GET.get('branche', '')
if q:
qs = qs.filter(name__icontains=q)
if branche:
qs = qs.filter(relevante_branchen__icontains=branche)
return render(request, 'marktbegleiter/marktbegleiter_liste.html', {
'marktbegleiter_liste': qs,
'q': q,
'branche': branche,
})
def marktbegleiter_neu(request):
form = MarktbegleiterForm(request.POST or None)
if form.is_valid():
obj = form.save()
return redirect('marktbegleiter:detail', pk=obj.pk)
return render(request, 'marktbegleiter/marktbegleiter_form.html', {'form': form, 'neu': True})
def marktbegleiter_bearbeiten(request, pk):
obj = get_object_or_404(Marktbegleiter, pk=pk)
form = MarktbegleiterForm(request.POST or None, instance=obj)
if form.is_valid():
form.save()
return redirect('marktbegleiter:detail', pk=obj.pk)
return render(request, 'marktbegleiter/marktbegleiter_form.html', {'form': form, 'obj': obj})
def marktbegleiter_detail(request, pk):
obj = get_object_or_404(Marktbegleiter, pk=pk)
passagen = Ausschreibungspassage.objects.filter(marktbegleiter=obj).select_related(
'ausschreibung', 'dokument'
).order_by('-verlaesslichkeitsscore')
anzahl_ausschreibungen = passagen.values('ausschreibung').distinct().count()
score_durchschnitt = passagen.aggregate(Avg('verlaesslichkeitsscore'))['verlaesslichkeitsscore__avg']
return render(request, 'marktbegleiter/marktbegleiter_detail.html', {
'obj': obj,
'passagen': passagen,
'anzahl_ausschreibungen': anzahl_ausschreibungen,
'score_durchschnitt': score_durchschnitt,
})
def marktbegleiter_auswertung(request, pk):
mb = get_object_or_404(Marktbegleiter, pk=pk)
passagen = Ausschreibungspassage.objects.filter(marktbegleiter=mb).select_related(
'ausschreibung', 'dokument'
)
ausschreiber_haeufigkeit = passagen.values(
'ausschreibung__ausschreiber'
).annotate(count=Count('id')).order_by('-count')[:10]
score_durchschnitt = passagen.aggregate(Avg('verlaesslichkeitsscore'))['verlaesslichkeitsscore__avg']
anzahl_ausschreibungen = passagen.values('ausschreibung').distinct().count()
return render(request, 'marktbegleiter/auswertung.html', {
'marktbegleiter': mb,
'passagen': passagen,
'ausschreiber_haeufigkeit': ausschreiber_haeufigkeit,
'score_durchschnitt': score_durchschnitt,
'anzahl_ausschreibungen': anzahl_ausschreibungen,
})