Kurs Python · Moduł 12: Projekt końcowy
Planowanie Projektu AI
W tej lekcji3
Witaj w finale Python Safari! Wyobraź sobie ekspedycję, która rusza w sawannę bez mapy, bez listy zapasów i bez celu. Po trzech dniach ktoś pyta "po co właściwie tu jesteśmy?", a nikt nie umie odpowiedzieć. Z projektami programistycznymi jest dokładnie tak samo: najczęściej nie upadają przez zły kod, tylko przez to, że nikt nie zapisał, jaki problem rozwiązują i kiedy można uznać je za skończone. W tym module połączysz wszystkie zdobyte umiejętności w jeden kompleksowy projekt, a zaczniemy od mapy ekspedycji.
Czego się nauczysz
- zapisywać problem i miary sukcesu projektu w kodzie Pythona, zamiast trzymać je w głowie
- formułować user stories z kryteriami akceptacji
- szacować zadania w story points i układać je w sprinty
- uzasadniać wybór stosu technologicznego
Metodologia planowania projektu
1. Definiowanie problemu
Zanim napiszemy pierwszą linię aplikacji, nazwijmy rodzaj projektu. Do zamkniętej listy wariantów Python ma typ Enum z modułu enum: każdy członek wyliczenia ma nazwę i wartość, a literówka w nazwie od razu kończy się błędem, zamiast cicho przejść dalej.
1from dataclasses import dataclass
2from enum import Enum
3
4class ProjectType(Enum):
5 WEB_APP = "web_application"
6 API = "api_service"
7 ML_PIPELINE = "ml_pipeline"
8 RAG_SYSTEM = "rag_system"
9 AUTOMATION = "automation"Na razie to tylko słownik rodzajów wypraw, nic się jeszcze nie dzieje. Teraz opiszmy samą wyprawę. Dekorator @dataclass z modułu dataclasses sam generuje __init__, __repr__ i porównywanie na podstawie pól z adnotacjami typów, więc klasa z danymi mieści się w kilku linijkach.
1@dataclass
2class ProjectDefinition:
3 """Definicja projektu."""
4 name: str
5 problem_statement: str
6 target_users: list[str]
7 success_metrics: list[str]
8 project_type: ProjectType
9 tech_stack: list[str]Adnotacje typu list[str] to dokumentacja i podpowiedź dla edytora oraz narzędzi takich jak mypy. Python ich nie wymusza w czasie działania, więc dataclass nie odrzuci złego typu. Zobaczmy wypełnioną definicję projektu:
1# Przykład
2capstone_project = ProjectDefinition(
3 name="AI Document Assistant",
4 problem_statement="Użytkownicy tracą czas na szukanie informacji w dokumentach",
5 target_users=["analitycy", "prawnicy", "badacze"],
6 success_metrics=[
7 "Redukcja czasu wyszukiwania o 70%",
8 "Dokładność odpowiedzi > 90%",
9 "Czas odpowiedzi < 2s"
10 ],
11 project_type=ProjectType.RAG_SYSTEM,
12 tech_stack=["Python", "FastAPI", "LlamaIndex", "Qdrant", "React"]
13)Najważniejsze są tu metryki sukcesu. "Czas odpowiedzi < 2s" da się zmierzyć, a "szybka aplikacja" już nie. To są cele, które sami sobie stawiamy, a nie gotowe dane: przy każdej metryce warto od razu zapisać, jak ją zmierzysz.
2. User Stories
User story to opis funkcji z perspektywy użytkownika, w stałym formacie "jako [kto] chcę [co], aby [po co]". Kryteria akceptacji mówią, kiedy historia jest skończona. W kodzie zapiszemy to kolejną dataclass:
1@dataclass
2class UserStory:
3 """User story w formacie Agile."""
4 as_a: str
5 i_want: str
6 so_that: str
7 acceptance_criteria: list[str]
8 priority: int # 1-5, 1 = najwyższyPole priority to zwykła liczba, a komentarz ustala umowę, że 1 oznacza najwyższy priorytet. Teraz dwie konkretne historie, dla analityka i dla administratora:
1stories = [
2 UserStory(
3 as_a="analityk",
4 i_want="zadać pytanie o dokumenty w języku naturalnym",
5 so_that="szybko znajdę potrzebne informacje",
6 acceptance_criteria=[
7 "System rozumie pytania po polsku",
8 "Odpowiedź zawiera źródła",
9 "Czas odpowiedzi < 3s"
10 ],
11 priority=1
12 ),
13 UserStory(
14 as_a="administrator",
15 i_want="łatwo dodawać nowe dokumenty",
16 so_that="baza wiedzy była aktualna",
17 acceptance_criteria=[
18 "Upload przez drag & drop",
19 "Obsługa PDF, DOCX, TXT",
20 "Automatyczna indeksacja"
21 ],
22 priority=2
23 )
24]Zwróć uwagę, że historie nie mówią nic o technologii. Analityk nie wie, czym jest wektorowa baza danych, i nie musi. Kryteria typu "Odpowiedź zawiera źródła" staną się później testami, więc pisz je tak, by dało się je sprawdzić.
3. Estymacja i planowanie sprintów
W Scrumie pracę dzieli się na sprinty, czyli odcinki o stałej długości, najwyżej miesiąc. W praktyce zespoły najczęściej wybierają dwa tygodnie. Zadania szacuje się w story points, czyli względnej złożoności, a nie w godzinach. Popularna skala to ciąg Fibonacciego (1, 2, 3, 5, 8, 13), bo rosnące przerwy między liczbami przypominają, że duże zadania są coraz mniej przewidywalne.
1from datetime import datetime, timedelta
2
3@dataclass
4class Task:
5 name: str
6 story_points: int # Fibonacci: 1, 2, 3, 5, 8, 13
7 dependencies: list[str]
8 assigned_to: str = ""Task pamięta też zależności, czyli nazwy zadań, które muszą być gotowe wcześniej. Sprint dostaje dwie właściwości liczone w locie dzięki @property: datę końca i sumę punktów.
1@dataclass
2class Sprint:
3 number: int
4 start_date: datetime
5 tasks: list[Task]
6 velocity: int = 20 # Story points per sprint
7
8 @property
9 def end_date(self) -> datetime:
10 return self.start_date + timedelta(days=14)
11
12 @property
13 def total_points(self) -> int:
14 return sum(t.story_points for t in self.tasks)velocity to liczba punktów, którą zespół realnie domyka w jednym sprincie. Zakładamy 20, ale prawdziwą wartość poznasz dopiero po kilku sprintach. Zobaczmy plan dwóch pierwszych etapów wyprawy:
1# Plan projektu
2sprints = [
3 Sprint(1, datetime(2024, 1, 1), [
4 Task("Setup projektu", 2, []),
5 Task("Konfiguracja CI/CD", 3, ["Setup projektu"]),
6 Task("Podstawowe API", 5, ["Setup projektu"]),
7 Task("Integracja Vector DB", 5, ["Podstawowe API"]),
8 ]),
9 Sprint(2, datetime(2024, 1, 15), [
10 Task("RAG Pipeline", 8, ["Integracja Vector DB"]),
11 Task("Chat interface", 5, ["Podstawowe API"]),
12 Task("Document upload", 5, ["RAG Pipeline"]),
13 ])
14]Kolejność wynika z zależności: najpierw setup, potem API, dopiero potem RAG. Pierwszy sprint ma 15 punktów, drugi 18, więc oba mieszczą się w założonym velocity i zostaje zapas na niespodzianki.
Wybór stosu technologicznego
Ostatni element mapy to decyzje technologiczne. Zapisz przy każdym wyborze powody, bo za pół roku ktoś (może Ty) zapyta, dlaczego akurat Qdrant.
1tech_decisions = {
2 "backend": {
3 "choice": "FastAPI",
4 "reasons": ["Async", "Type hints", "Auto docs", "Performance"]
5 },
6 "vector_db": {
7 "choice": "Qdrant",
8 "reasons": ["Self-hosted", "Filtering", "Rust performance"]
9 },
10 "llm": {
11 "choice": "OpenAI API",
12 "reasons": ["Quality", "Function calling", "Reliable"]
13 },
14 "rag_framework": {
15 "choice": "LlamaIndex",
16 "reasons": ["Flexibility", "Integrations", "Community"]
17 }
18}Przy modelu językowym celowo zapisaliśmy dostawcę, a nie konkretny model. Nazwy modeli zmieniają się co kilka miesięcy, a starsze wersje są wycofywane, więc aktualny model wybierz z listy w dokumentacji dostawcy w dniu startu projektu. Moja rada: trzymaj tę listę decyzji w repozytorium obok kodu, a nie w notatkach, bo wtedy żyje razem z projektem.
Cały cykl życia projektu to kolejno planowanie, implementacja, testowanie, deployment i monitoring. Instalację zależności z pliku requirements.txt robi się poleceniem pip install -r requirements.txt, a konkretną wersję pakietu przypinasz zapisem nazwa==wersja, na przykład pip install numpy==1.26.4. W następnej lekcji zaprojektujemy architekturę systemu, która zamieni ten plan w strukturę kodu.
Pamiętaj: dobry plan ekspedycji to połowa sukcesu, bo mapa narysowana przed wyjazdem oszczędza tygodni błądzenia po sawannie.
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Co to jest User Story w metodologii Agile?
2. Ile trwa typowy Sprint w metodyce Scrum?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj kroki:
- Edytor kodu
Zaimplementuj ProjectDefinition dataclass
- Układanie w poziomie
Ułóż elementy:
- Klikanie w kolejności
Kliknij w kolejności: