Разработка прототипа информационной системы от архитектуры до отладки.
Что вы будете делать
Сделаем мини-ИС «Колледж-Библиотека»:
- GUI-клиент для библиотекаря: добавление/поиск книг, выдача/возврат.
- Локальный API-сервер (Flask), обмен в JSON.
- Файлы как хранилище (CSV/JSON), без БД.
- Простой «экспертный» модуль рекомендаций по истории выдач.
- Работа в Git с ветками и ревью (командная модель).
Необходимо объединиться в группы по 5 человек. Распределить роли в команде:
- Менеджер (1 человек)
- Тестировщик (1 человек)
- Разработчик (3 человека)
Присылаемые отчеты содержат отражение именно своей роли в проекте. Перед экзаменом отчеты будут выгружены из БД igorkolosov.ru b объединены по группам. Исходя из личного вклада каждого в проект будет зависеть в большей степени оценка на экзамене.
Для этого, выбранный менеджер отправляет список команды (с указанием группы, т.к. можно объединяться в команды из разных групп), в t.me/itgor с тегом в тексте сообщения #rkis4
Шаг 0. Подготовка окружения (один раз)
Цель: чтобы у каждого проект запускался одинаково.
- Установите Python 3.11+.
- Создайте папку проекта, затем виртуальную среду:
Windows (PowerShell):
python -m venv .venv
.venv\Scripts\Activate.ps1
macOS/Linux (bash/zsh):
python3 -m venv .venv
source .venv/bin/activate
- Установим зависимости (пока пусто, добавим позже):
pip install --upgrade pip
Если на Linux нет Tkinter:
sudo apt-get install python3-tk.
Шаг 1. Репозиторий и правила (Git + GitHub)
Зачем: пп. 5–7 темы 5.2.2 (репозиторий, доступы, версии).
- В корне проекта:
git init
echo ".venv/" > .gitignore
git add .
git commit -m "Init repo"
- Создайте приватный репозиторий на GitHub
college-library-<ФамилияИнициалы>. - Настройте правила:
- Каждая контрольная точка (КП1–КП10) — отдельная ветка
cp<N>, PR вmain. - Каждый PR содержит Markdown-отчёт
reports/CP<N>.md. - Скриншоты запрещены. Только текст/код.
- Минимум 1 ревью от сокурсника.
Шаг 2. Каркас проекта (создаём файлы)
Зачем: п.3 «Шаблон проекта».
Создайте структуру:
/src
/client_gui
app.py
ui_screens.py
services.py
/server
api.py
storage.py
recommender.py
/common
models.py
io_utils.py
/data
books.csv
users.csv
issues.json
/docs
architecture.md
ui_requirements.md
coding_style.md
settings_spec.md
/reports
CP1.md ... CP10.md
README.md
requirements.txt
Содержимое заготовок пока пустое (или по 1 строке «TODO»).
Сделайте коммит: git add . && git commit -m "Scaffold".
Шаг 3. Зависимости и README
Зачем: пп. 4, 6–7 (конфигурация, настройки среды, версии).
requirements.txt:
Flask==3.0.0
requests==2.32.3
README.md (минимум):
# Колледж-Библиотека
## Запуск
1) python -m venv .venv
2) source .venv/bin/activate (Windows: .venv\Scripts\Activate.ps1)
3) pip install -r requirements.txt
4) В одном терминале: python src/server/api.py
5) В другом: python src/client_gui/app.py
Коммит.
Шаг 4. Общие модели данных
Зачем: пп. 9–10 (спецификация языка/синтаксис/стиль, основные конструкции).
src/common/models.py
from dataclasses import dataclass, asdict
from typing import Optional, Dict, Any
from datetime import datetime
ISO = "%Y-%m-%d"
@dataclass
class Book:
isbn: str
title: str
author: str
genre: str
total_copies: int
available_copies: int
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@staticmethod
def from_dict(d: Dict[str, Any]) -> "Book":
return Book(
isbn=str(d["isbn"]),
title=str(d["title"]),
author=str(d["author"]),
genre=str(d.get("genre", "")),
total_copies=int(d["total_copies"]),
available_copies=int(d["available_copies"]),
)
@dataclass
class User:
user_id: str
name: str
def to_dict(self): return asdict(self)
@dataclass
class IssueRecord:
user_id: str
isbn: str
issued_on: str # YYYY-MM-DD
returned_on: Optional[str] = None
def to_dict(self): return asdict(self)
@staticmethod
def today() -> str:
return datetime.now().strftime(ISO)
Шаг 5. Утилиты ввода-вывода (CSV/JSON)
Зачем: п.17 «Файловый ввод-вывод», п.15 «источники/приёмники».
src/common/io_utils.py
import csv, json, os, tempfile
from typing import List, Dict, Any
def read_csv(path: str) -> List[Dict[str, Any]]:
if not os.path.exists(path): return []
with open(path, newline="", encoding="utf-8") as f:
return list(csv.DictReader(f))
def write_csv(path: str, rows: List[Dict[str, Any]]):
if not rows:
# создаём файл с шапкой по пустой схеме:
with open(path, "w", newline="", encoding="utf-8") as f:
pass
return
fieldnames = list(rows[0].keys())
with open(path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=fieldnames)
w.writeheader()
w.writerows(rows)
def read_json(path: str):
if not os.path.exists(path): return []
with open(path, encoding="utf-8") as f:
try:
return json.load(f)
except json.JSONDecodeError:
# повреждённый файл — вернём пустой массив, чтобы не падать
return []
def safe_write_json(path: str, data):
# атомарная запись через временный файл
dir_ = os.path.dirname(path) or "."
fd, tmp = tempfile.mkstemp(dir=dir_, prefix=".tmp_", suffix=".json")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
os.replace(tmp, path)
finally:
if os.path.exists(tmp):
try: os.remove(tmp)
except: pass
Шаг 6. Хранилище сервера
Зачем: подготовка к API, интеграции модулей (пп. 14–17).
src/server/storage.py
from typing import List, Optional
from pathlib import Path
from common.io_utils import read_csv, write_csv, read_json, safe_write_json
from common.models import Book, IssueRecord
DATA = Path(__file__).resolve().parents[2] / "data"
BOOKS = DATA / "books.csv"
USERS = DATA / "users.csv" # (на будущее)
ISSUES = DATA / "issues.json"
class Storage:
def load_books(self) -> List[Book]:
rows = read_csv(str(BOOKS))
return [Book.from_dict({
"isbn": r.get("isbn",""),
"title": r.get("title",""),
"author": r.get("author",""),
"genre": r.get("genre",""),
"total_copies": int(r.get("total_copies", 0) or 0),
"available_copies": int(r.get("available_copies", 0) or 0),
}) for r in rows]
def save_books(self, books: List[Book]):
rows = [b.to_dict() for b in books]
write_csv(str(BOOKS), rows)
def load_issues(self) -> List[IssueRecord]:
data = read_json(str(ISSUES)) or []
out: List[IssueRecord] = []
for d in data:
out.append(IssueRecord(
user_id=str(d["user_id"]),
isbn=str(d["isbn"]),
issued_on=str(d["issued_on"]),
returned_on=d.get("returned_on")
))
return out
def save_issues(self, issues: List[IssueRecord]):
safe_write_json(str(ISSUES), [i.to_dict() for i in issues])
# вспомогательные:
def find_book(self, books: List[Book], isbn: str) -> Optional[Book]:
for b in books:
if b.isbn == isbn: return b
return None
Шаг 7. API-сервер (Flask)
Зачем: пп. 11, 16 «сетевой сервер/клиент», «транспорт/форматы сообщений».
src/server/api.py
from flask import Flask, request, jsonify
from common.models import Book, IssueRecord
from server.storage import Storage
from datetime import datetime
app = Flask(__name__)
st = Storage()
def error(message, code=400):
return jsonify({"ok": False, "error": message}), code
@app.get("/books")
def books_get():
"""Поиск книг: /books?q=строка (ищет по названию/автору/жанру)"""
q = (request.args.get("q") or "").strip().lower()
books = st.load_books()
if not q:
return jsonify({"ok": True, "items": [b.to_dict() for b in books]})
res = []
for b in books:
hay = f"{b.title} {b.author} {b.genre}".lower()
if q in hay: res.append(b.to_dict())
return jsonify({"ok": True, "items": res})
@app.post("/books")
def books_add():
"""Добавление книги (для простоты без авторизации)."""
data = request.get_json(force=True, silent=True) or {}
need = ["isbn","title","author","genre","total_copies"]
for k in need:
if k not in data: return error(f"missing field: {k}")
books = st.load_books()
if any(b.isbn == data["isbn"] for b in books):
return error("book already exists", 409)
total = int(data["total_copies"])
b = Book(
isbn=str(data["isbn"]),
title=str(data["title"]),
author=str(data["author"]),
genre=str(data.get("genre","")),
total_copies=total,
available_copies=total
)
books.append(b)
st.save_books(books)
return jsonify({"ok": True, "item": b.to_dict()}), 201
@app.post("/issue")
def issue_book():
"""Выдача: {user_id, isbn}"""
data = request.get_json(force=True, silent=True) or {}
for k in ["user_id","isbn"]:
if k not in data: return error(f"missing field: {k}")
books = st.load_books()
book = st.find_book(books, str(data["isbn"]))
if not book: return error("book not found", 404)
if book.available_copies <= 0: return error("no copies available", 409)
issues = st.load_issues()
# проверим, что у пользователя нет активной выдачи этой книги
for i in issues:
if i.user_id == data["user_id"] and i.isbn == book.isbn and i.returned_on is None:
return error("already issued to this user", 409)
issues.append(IssueRecord(
user_id=str(data["user_id"]),
isbn=book.isbn,
issued_on=datetime.now().strftime("%Y-%m-%d"),
returned_on=None
))
book.available_copies -= 1
st.save_books(books); st.save_issues(issues)
return jsonify({"ok": True})
@app.post("/return")
def return_book():
"""Возврат: {user_id, isbn}"""
data = request.get_json(force=True, silent=True) or {}
for k in ["user_id","isbn"]:
if k not in data: return error(f"missing field: {k}")
books = st.load_books()
book = st.find_book(books, str(data["isbn"]))
if not book: return error("book not found", 404)
issues = st.load_issues()
found = False
for i in issues:
if i.user_id == data["user_id"] and i.isbn == book.isbn and i.returned_on is None:
i.returned_on = datetime.now().strftime("%Y-%m-%d")
found = True
break
if not found: return error("active issue not found", 404)
book.available_copies += 1
st.save_books(books); st.save_issues(issues)
return jsonify({"ok": True})
@app.errorhandler(500)
def _e500(e):
return error("internal error", 500)
if __name__ == "__main__":
app.run(port=5001, debug=True)
Запуск сервера:
python src/server/api.py
Шаг 8. Клиентские сервисы (вызовы API)
src/client_gui/services.py
import requests
API = "http://127.0.0.1:5001"
class ApiError(Exception): pass
def _req(method, path, **kw):
try:
r = requests.request(method, API + path, timeout=5, **kw)
except requests.RequestException as e:
raise ApiError(f"network error: {e}")
try:
data = r.json()
except Exception:
raise ApiError(f"http {r.status_code}: invalid json")
if not data.get("ok"):
raise ApiError(data.get("error","unknown error"))
return data
def books_search(q: str):
return _req("GET", "/books", params={"q": q}).get("items", [])
def books_add(item: dict):
return _req("POST", "/books", json=item).get("item")
def issue(user_id: str, isbn: str):
return _req("POST", "/issue", json={"user_id": user_id, "isbn": isbn})
def return_(user_id: str, isbn: str):
return _req("POST", "/return", json={"user_id": user_id, "isbn": isbn})
Шаг 9. GUI (Tkinter, минимал)
Зачем: пп. 8, 12 «требования к UI, разработка GUI».
src/client_gui/ui_screens.py
import tkinter as tk
from tkinter import messagebox
from .services import books_search, books_add, issue, return_, ApiError
class MainUI(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.pack(fill="both", expand=True)
# Поиск
tk.Label(self, text="Поиск книги:").pack(anchor="w")
row = tk.Frame(self); row.pack(fill="x")
self.q = tk.Entry(row); self.q.pack(side="left", fill="x", expand=True)
tk.Button(row, text="Искать", command=self.on_search).pack(side="right")
self.listbox = tk.Listbox(self, height=12); self.listbox.pack(fill="both", expand=True, pady=6)
# Действия
act = tk.Frame(self); act.pack(fill="x")
tk.Button(act, text="Выдать", command=self.on_issue).pack(side="left")
tk.Button(act, text="Принять возврат", command=self.on_return).pack(side="left")
# Добавление книги (минимум)
add = tk.Frame(self); add.pack(fill="x", pady=8)
tk.Label(add, text="Добавить книгу (isbn;title;author;genre;total):").pack(anchor="w")
self.add_e = tk.Entry(add); self.add_e.pack(fill="x")
tk.Button(add, text="Добавить", command=self.on_add).pack(anchor="e")
self.user_id = "u001" # упрощение: фиксированный читатель
def _selected_isbn(self):
s = self.listbox.curselection()
if not s:
messagebox.showinfo("Инфо","Выберите книгу в списке"); return None
raw = self.listbox.get(s[0])
# формат: "<isbn> :: <title> by <author> (avail/total)"
return raw.split(" :: ",1)[0]
def on_search(self):
items = books_search(self.q.get().strip())
self.listbox.delete(0, tk.END)
for it in items:
line = f'{it["isbn"]} :: {it["title"]} by {it["author"]} ({it["available_copies"]}/{it["total_copies"]})'
self.listbox.insert(tk.END, line)
def on_add(self):
try:
isbn,title,author,genre,total = [x.strip() for x in self.add_e.get().split(";")]
total = int(total)
item = {
"isbn": isbn, "title": title, "author": author,
"genre": genre, "total_copies": total
}
books_add(item)
messagebox.showinfo("OK", "Книга добавлена")
self.on_search()
except ValueError:
messagebox.showerror("Ошибка","Неверный формат. Пример: 978-5-..;Название;Автор;Жанр;2")
except ApiError as e:
messagebox.showerror("API", str(e))
def on_issue(self):
isbn = self._selected_isbn()
if not isbn: return
try:
issue(self.user_id, isbn)
messagebox.showinfo("OK","Выдано")
self.on_search()
except ApiError as e:
messagebox.showerror("API", str(e))
def on_return(self):
isbn = self._selected_isbn()
if not isbn: return
try:
return_(self.user_id, isbn)
messagebox.showinfo("OK","Принято")
self.on_search()
except ApiError as e:
messagebox.showerror("API", str(e))
src/client_gui/app.py
import tkinter as tk
from .ui_screens import MainUI
if __name__ == "__main__":
root = tk.Tk()
root.title("Колледж-Библиотека")
root.geometry("720x520")
MainUI(root)
root.mainloop()
Запуск GUI (в другом терминале, пока сервер работает):
python src/client_gui/app.py
Шаг 10. Рекомендации («экспертный» модуль)
Зачем: п.15 (сопоставление данных), п.19 (спецификация настроек).
src/server/recommender.py
from collections import Counter
from typing import List, Dict
from common.models import IssueRecord, Book
def recommend_for_user(user_id: string, books: List[Book], issues: List[IssueRecord], k: int = 3):
# простая эвристика: жанры, которые чаще выдавал этот пользователь,
# плюс самые популярные в целом
user_genres = Counter()
global_genres = Counter()
by_isbn = {b.isbn: b for b in books}
for it in issues:
b = by_isbn.get(it.isbn)
if not b: continue
global_genres[b.genre] += 1
if it.user_id == user_id:
user_genres[b.genre] += 1
# приоритезируем жанры пользователя, затем популярные
order = [g for g,_ in user_genres.most_common()] + [g for g,_ in global_genres.most_common()]
seen = set(); out = []
for g in order:
for b in books:
if b.genre == g and b.available_copies > 0 and b.isbn not in seen:
out.append(b); seen.add(b.isbn)
if len(out) >= k: return out
# fallback: любые доступные
for b in books:
if b.available_copies > 0 and b.isbn not in seen:
out.append(b)
if len(out) >= k: break
return out
(По желанию добавьте эндпоинт /reco?user_id=....)
Шаг 11. Логирование, исключения, «отладочный класс»
Зачем: пп. 13, 18 — отладка, обработка исключений, отладочные классы.
Добавьте в src/server/api.py:
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("api")
И логируйте ключевые события:
log.info("ISSUE user=%s isbn=%s", data["user_id"], data["isbn"])
Простой «отладочный миксин»:
class TraceMixin:
def trace(self, tag, **kw):
logging.info("TRACE %s %s", tag, kw)
Наследуйте Storage(TraceMixin) и расставьте .trace(...) перед критическими шагами (чтение/запись файлов, изменение остатков).
Шаг 12. События/интеграция (BOOK_OVERDUE)
Зачем: п.14 «уровни интеграции», п.13 «обработка исключений» (частично).
Идея проста: при поиске просрочки (сравниваете issued_on + лимит дней из настроек) — формируете событие BOOK_OVERDUE и показываете в GUI текстовое уведомление (без сложной шины событий; просто функция check_overdues() в GUI, вызываемая по кнопке «Проверить просрочки»).
Шаг 13. Данные для старта
data/books.csv (пример):
isbn,title,author,genre,total_copies,available_copies
978-5-17-118366-2,Алгебра добра,И. Петров,Учебник,2,2
978-5-389-07402-6,Чистый код,Р. Мартин,IT,1,1
978-5-17-070773-9,Мастер и Маргарита,М. Булгаков,Художественная,3,3
data/users.csv — можно не задействовать (у нас фиксированный u001).data/issues.json — изначально [].
Шаг 14. Контрольные точки (КП1–КП10): что именно сдавать
На каждую КП — ветка + PR + reports/CP<N>.md. Только текст/код. Ниже — минимальные шаблоны.
В каждой КП есть микро-теория (что прочитать/понять) и 2 “негуглябельных” вопроса — отвечайте про ваш проект.
КП1 — Выбор модели и средств (пп. 1–2)
Сделать: коротко описать предметную область, выбрать модель (клиент–сервер), обосновать инструменты.
Отчёт CP1.md шаблон:
# КП1 — Модель и средства
## Контекст
(3–5 предложений: что такое "Колледж-Библиотека" у вас)
## Модель ИС
(1–2 абзаца: клиент-сервер, файлы как хранилище, почему)
## Выбор средств
| Инструмент | Зачем | Риск/ограничение |
| Python | ... | ... |
| Flask | ... | ... |
| JSON | ... | ... |
Микро-теория: модель развертывания, причинно-следственная связь «решение → последствия».
Вопросы:
- В какой точке ваш выбор упростил проект, а где усложнил (по 1 примеру)?
- Чем ваша модель лучше монолита для этой ИС — именно в ваших задачах?
КП2 — Архитектура и шаблон (п. 3)
Сделать: описать слои (GUI / API / storage / common), создать каркас, docs/architecture.md (ASCII-схема, потоки данных).
Отчёт:
# КП2 — Архитектура
## Слои и модули
(списком: модуль → ответственность)
## Потоки данных
(текстом: кто кого вызывает, какие JSON-поля идут)
## Почему так проще отлаживать
(3 пункта)
Микро-теория: низкая связность/высокая когезия на вашем примере.
Вопросы:
- Какой модуль пострадает первым при смене формата JSON и почему?
- Что будете делать, если решите добавить авторизацию?
КП3 — Конфигурация + Git/роли (пп. 4–7)
Сделать: README.md, requirements.txt, политика ветвления/PR, сценарий первого запуска.
Отчёт:
# КП3 — Конфигурация/репозиторий
## Требования и запуск
(точная последовательность команд)
## Политика ветвления/PR
(ветки cpN → PR → ревью → merge)
## Роли
(кто кого ревьюит и почему)
Микро-теория: зачем контроль версий даже «для одного».
Вопросы:
- Какие 2 риска снимают правила PR в вашей группе?
- Какие 2 метрики вы бы логировали для «мониторинга разработки»?
КП4 — Требования к UI + прототип GUI (пп. 8, 12)
Сделать: docs/ui_requirements.md со сценариями, ASCII-макеты, «черновой» GUI.
Отчёт:
# КП4 — UI
## Сценарии
- Поиск книги: ...
- Выдача: ...
- Возврат: ...
## Экраны (словами)
(какие поля/кнопки, зачем)
## Пример диалога
(пошагово, без скринов)
Микро-теория: 2 правила UX, которые применили, и почему.
Вопросы:
- Что ухудшится в UX, если объединить выдачу и возврат в одну форму?
- Почему в вашем UI нельзя скрывать ошибку «нет доступных экземпляров»?
КП5 — Стиль кода + базовые алгоритмы (пп. 9–10)
Сделать: docs/coding_style.md, реализация поиска/фильтра/расчёта штрафа/генератора номера.
Отчёт:
# КП5 — Стиль/алгоритмы
## 5 правил стиля с примерами "до/после"
(код-блоки на 3–6 строк)
## Поисковые запросы (примеры)
(текстом)
## Формула штрафа
(слова + пример расчёта)
Микро-теория: почему единый стиль ускоряет чтение кода.
Вопросы:
- Какая деталь вашего стиля уменьшает шанс на баг и почему?
- Почему генератор случайного кода лучше делать детерминируемым в тестах?
КП6 — Файловый ввод-вывод (пп. 15, 17)
Сделать: реализация чтения/записи CSV/JSON, инварианты форматов.
Отчёт:
# КП6 — Файлы
## Форматы
(таблица полей CSV/JSON; типы; инварианты)
## Примеры записей
(код-блоки)
## Ошибки/исключения
(какие ловим, какой текст показываем)
Микро-теория: почему JSON повреждается и как это заметить заранее.
Вопросы:
- Что именно в ваших инвариантах предотвращает «полу-битые» строки?
- Как восстановиться, если пропала доступность файла во время записи?
КП7 — Сервер/клиент, формат сообщений (пп. 11, 16)
Сделать: 3 эндпоинта (/books, /issue, /return), контракт JSON, коды ошибок; GUI реально ходит к API.
Отчёт:
# КП7 — API
## Контракты
/endpoint → вход/выход (список полей)
/issue → {...} → {...}
/return → {...} → {...}
## Отказоустойчивость
(какие ошибки и тексты сообщений)
Микро-теория: почему JSON удобнее CSV для API.
Вопросы:
- Какие 2 ошибки транспорта вы смоделировали и чем их закрыли?
- Как повлияло бы добавление пагинации на ваш GUI?
КП8 — Интеграция модулей (п. 14)
Сделать: связать GUI↔API↔storage; событие BOOK_OVERDUE (текстовое уведомление).
Отчёт:
# КП8 — Интеграция
## Схема (словами)
GUI → API → storage → файлы → API → GUI
## Событие и обработчик
(короткий код)
## 2 интеграционных теста (псевдокод)
(шаги и ожидаемый результат)
Микро-теория: уровни интеграции и где у вас «стыки».
Вопросы:
- Где вы поставили «границу» между модульными и интеграционными тестами и почему?
- Какое место интеграции уязвимее в вашей схеме и почему?
КП9 — Отладка и исключения (пп. 13, 18)
Сделать: централизованный перехват исключений, логирование, «отладочный миксин», 3 смоделированные ошибки.
Отчёт:
# КП9 — Отладка
## Где и почему перехватываем
(сервер/GUI; аргументы)
## Формат лог-строки
(код-блок)
## 3 смоделированные ошибки
(входные данные + ожидаемый текст)
Микро-теория: «не проглатывайте» какие-то исключения — какие и почему.
Вопросы:
- Объясните по 3 строкам реального trace, что там происходило.
- Какая ошибка должна «валить» приложение, а какая — только показывать сообщение?
КП10 — Рекомендации + спецификация настроек (п. 19; и 15 частично)
Сделать: модуль recommender.py; docs/settings_spec.md — таблица всех настраиваемых параметров (пути файлов, лимит дней, штраф/день, пороги рекомендаций, флаги debug).
Отчёт:
# КП10 — Рекомендации/настройки
## Правила рекомендаций
(словами + пример вход/выход)
## Таблица настроек
| Ключ | Тип | Диапазон | По умолчанию | Что меняется |
## Эффект изменений
(3 примера влияния)
Микро-теория: что выносить в конфиг, а что оставлять в коде.
Вопросы:
- Почему именно эти настройки стоит вынести в конфиг в вашем проекте?
- Как убедиться, что рекомендации не «тормозят» при росте данных?
Частые ошибки и как их не допустить
- Пустые файлы CSV/JSON ломают парсинг → в
io_utilsуже предусмотрен безопасный парсинг. - Смешение слоёв (GUI лезет в файлы) → всегда через API.
- Нет сообщений об ошибках → пользователь должен получать понятный текст (покажите в отчёте).
- PR без отчёта → отклонять. Без
reports/CP<N>.mdКП не засчитывается. - Скриншоты → запрещены.
Чек-лист качества перед сдачей
- Проект запускается с нуля по шагам из
README.md. - GUI работает: поиск/выдача/возврат, тексты ошибок понятные.
- Файлы обновляются после операций.
- Логи пишутся, есть 3 смоделированные ошибки.
- Для каждой КП есть отчёт (Markdown), ответы на 2 вопроса.
Финальный запуск (памятка)
- Терминал A:
source .venv/bin/activate
pip install -r requirements.txt
python src/server/api.py
- Терминал B:
source .venv/bin/activate
python src/client_gui/app.py
- В GUI:
- Поиск → Добавить книгу → Выдать → Возврат → Проверка сообщений/ошибок.
1/10
[task_id id=»20077″]
2/10
[task_id id=»20078″]
3/10
[task_id id=»20079″]
4/10
[task_id id=»20080″]
5/10
[task_id id=»20081″]
6/10
[task_id id=»20082″]
7/10
[task_id id=»20086″]
8/10
[task_id id=»20087″]
9/10
[task_id id=»20088″]
10/10
[task_id id=»20089″]
Проектирование и дизайн
- Всё сдаётся в том же репозитории и в том же стиле (ветка → PR →
reports/…md, без скриншотов). - Для 05.01 Проектирование и дизайн ИС добавляем ещё 8 контрольных точек: A1–A8 (A = Analysis/Architecture/Assessment).
- Каждая A-точка — отдельная ветка
cpA<N>и отдельный файл отчётаreports/CPA<N>.md. - Только текст: диаграммы — в ASCII, таблицы — в Markdown.
Следующие контрольные точки
A1 — Предпроектное обследование и жизненный цикл
Что сдать (reports/CPA1.md):
- Жизненный цикл вашей ИС (этапы и вход/выход каждого этапа) — списком на 10–14 пунктов.
- Анализ предметной области: цель, заинтересованные стороны, объекты, события, ограничения.
- Постановка задач обработки данных: какие входы/выходы, какие операции и почему.
- План управления проектом (микроуровень на семестр): вехи, роли, риски, критерии готовности.
ASCII-шаблон для этапа:
Этап: Анализ → Выход: Список требований v0.1
Вход: интервью, учебный план
Методы: контент-анализ; сценарный анализ
Риски: неполнота требований; меры: итерации раз/неделю
Проверочные вопросы:
- Какой этап жизненного цикла будет “бутылочным горлышком” именно в вашей ИС и почему?
- Какая одна метрика покажет, что «анализ» провалился, даже если разработка идет?
A2 — Модели ИС и SOA-вариант
Что сдать (reports/CPA2.md):
- Сравнение 3 моделей построения (напр., монолит/клиент-сервер/SOA для нашей задачи) — таблицей критериев.
- SOA-набросок: какие сервисы были бы выделены (каталог, учёт выдач, рекомендации), их контракты (словами).
Таблица примера (сокращённо):
| Критерий | Монолит | Кл-сервер (наш) | SOA (вариант) |
| Сложность внедр. | низкая | средняя | высокая |
| Эволюция модулей | низкая | средняя | высокая |
| Нагрузка | низкая | средняя | высокая |
Вопросы:
- Если завтра добавится «мобильная выдача», какая модель дешевле в сопровождении — и почему именно для ваших модулей?
- Какой один контракт в SOA вы бы сделали идемпотентным — и зачем?
A3 — CASE-моделирование и IDEF0
Что сдать (reports/CPA3.md):
- Контекстная IDEF0-диаграмма A-0 (ASCII) с целью/точкой зрения.
- Декомпозиция на A0 → A1..A3 с перечислением работ и стрелок (вход/управление/механизм/выход — текстом).
- Коротко — слияние/расщепление моделей, нумерация, «туннелирование» стрелок (если применимо) — словесно, с упоминанием где и зачем.
Пример ASCII фрагмента:
[ A0 Управление библиотекой ]
I: Запросы читателя
C: Правила выдачи
M: Библиотекарь, Сервер API
O: Выданные/возвращённые книги
Вопросы:
- Какое одно управление (Control) в вашей A0 критично перепутают с входом (Input) и как это повлияет на модель?
- Где «туннелирование» улучшит читаемость именно вашей схемы?
A4 — Состав обеспечения и экспертные/реального времени
Что сдать (reports/CPA4.md):
- Таблица информационного/программного/технического обеспечения (что уже есть и что нужно).
- Кратко — чем «экспертный модуль рекомендаций» отличается от полноценной экспертной системы (на вашем примере).
- Почему ваша ИС не система реального времени и что пришлось бы изменить, чтобы ей стать.
Вопросы:
- Какой элемент программного обеспечения у вас — «точка наибольшего изменения» и почему?
- Если добавить «реальное время» (жесткие дедлайны отклика), какое решение станет первым узким местом?
A5 — Экономическая эффективность и выбор средств
Что сдать (reports/CPA5.md):
- Стоимостная оценка по стадиям (оценка порядка величины → концептуальная → предварительная → окончательная → контрольная) — текст и Markdown-таблицы.
- Обоснование выбора текущих средств проектирования (почему без БД, почему Flask/Tk) в ценовом выражении: трудозатраты, риски, стоимость смены технологии.
Вопросы:
- Какой один риск дороже всего в вашей оценке и почему вы его “купили”?
- Какие 2 метрики вы будете мониторить для пост-фактум сверки с оценкой?
A6 — Качество, безопасность, реинжиниринг
Что сдать (reports/CPA6.md):
- Модель управления качеством для вашего учебного проекта: цели качества, артефакты контроля, точки измерений (входных/выходных), критерии приёмки.
- Требования безопасности: роли/права (хотя бы текстом), защита данных файлов, обработка ошибок без утечек, базовые меры.
- Реинжиниринг процесса: выберите один подпроцесс (например, выдача), предложите 2 способа улучшения (горизонтальное/вертикальное «сжатие») и ожидаемый эффект.
Вопросы:
- Какие 2 ошибки качества у вас наиболее вероятны именно из-за учебного формата?
- Какая одна мера безопасности даёт наибольший выигрыш при минимальной цене для вашей ИС?
A7 — Документация по ЕСПД/ЕСКД и ТЗ
Что сдать (reports/CPA7.md):
- Перечень и комплектность документации, которую вы реально сможете поддерживать в учебном проекте (списком, с назначением).
- Черновик ТЗ (разделы и краткое содержание: назначение, функции, интерфейсы, ограничения, проверки, критерии приёмки, стадии и источники данных).
- Сетевой график (ASCII) ключевых вех/зависимостей — без картинок.
Вопросы:
- Какой один раздел ТЗ у вас будет «живым» (обновляемым) и почему?
- Где сетевой график “врёт”, но полезен — применительно к вашему графику?
A8 — Пользовательская и инсталляционная доки, самодок-код
Что сдать (reports/CPA8.md):
- Руководство по инсталляции: пошагово (команды/варианты ОС/типичные ошибки).
- Руководство пользователя: 3 сценария (поиск/выдача/возврат) — линейные чек-листы.
- Пример «самодокументирующегося» кода: выделить 3 фрагмента и показать, какие имена/структура снимают необходимость комментариев.
Вопросы:
- Какая одна неоднозначность в терминах пользователя может “сломать” поддержку?
- Какой фрагмент кода вы упростили так, что комментарий стал не нужен — и почему?
1/8
[task_id id=»20117″]
2/8
[task_id id=»20118″]
3/8
[task_id id=»20119″]
4/8
[task_id id=»20120″]
5/8
[task_id id=»20121″]
6/8
[task_id id=»20122″]
7/8
[task_id id=»20123″]
8/8
[task_id id=»20124″]