Разработка прототипа информационной системы от архитектуры до отладки.

Что вы будете делать

Сделаем мини-ИС «Колледж-Библиотека»:

  • GUI-клиент для библиотекаря: добавление/поиск книг, выдача/возврат.
  • Локальный API-сервер (Flask), обмен в JSON.
  • Файлы как хранилище (CSV/JSON), без БД.
  • Простой «экспертный» модуль рекомендаций по истории выдач.
  • Работа в Git с ветками и ревью (командная модель).

Необходимо объединиться в группы по 5 человек. Распределить роли в команде:

  • Менеджер (1 человек)
  • Тестировщик (1 человек)
  • Разработчик (3 человека)

Присылаемые отчеты содержат отражение именно своей роли в проекте. Перед экзаменом отчеты будут выгружены из БД igorkolosov.ru b объединены по группам. Исходя из личного вклада каждого в проект будет зависеть в большей степени оценка на экзамене.

Для этого, выбранный менеджер отправляет список команды (с указанием группы, т.к. можно объединяться в команды из разных групп), в t.me/itgor с тегом в тексте сообщения #rkis4


Шаг 0. Подготовка окружения (один раз)

Цель: чтобы у каждого проект запускался одинаково.

  1. Установите Python 3.11+.
  2. Создайте папку проекта, затем виртуальную среду:

Windows (PowerShell):

python -m venv .venv
.venv\Scripts\Activate.ps1

macOS/Linux (bash/zsh):

python3 -m venv .venv
source .venv/bin/activate
  1. Установим зависимости (пока пусто, добавим позже):
pip install --upgrade pip

Если на Linux нет Tkinter: sudo apt-get install python3-tk.


Шаг 1. Репозиторий и правила (Git + GitHub)

Зачем: пп. 5–7 темы 5.2.2 (репозиторий, доступы, версии). 

  1. В корне проекта:
git init
echo ".venv/" > .gitignore
git add .
git commit -m "Init repo"
  1. Создайте приватный репозиторий на GitHub college-library-<ФамилияИнициалы>.
  2. Настройте правила:
  • Каждая контрольная точка (КП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. В какой точке ваш выбор упростил проект, а где усложнил (по 1 примеру)?
  2. Чем ваша модель лучше монолита для этой ИС — именно в ваших задачах?

КП2 — Архитектура и шаблон (п. 3) 

Сделать: описать слои (GUI / API / storage / common), создать каркас, docs/architecture.md (ASCII-схема, потоки данных).
Отчёт:

# КП2 — Архитектура
## Слои и модули
(списком: модуль → ответственность)
## Потоки данных
(текстом: кто кого вызывает, какие JSON-поля идут)
## Почему так проще отлаживать
(3 пункта)

Микро-теория: низкая связность/высокая когезия на вашем примере.
Вопросы:

  1. Какой модуль пострадает первым при смене формата JSON и почему?
  2. Что будете делать, если решите добавить авторизацию?

КП3 — Конфигурация + Git/роли (пп. 4–7) 

Сделать: README.mdrequirements.txt, политика ветвления/PR, сценарий первого запуска.
Отчёт:

# КП3 — Конфигурация/репозиторий
## Требования и запуск
(точная последовательность команд)
## Политика ветвления/PR
(ветки cpN → PR → ревью → merge)
## Роли
(кто кого ревьюит и почему)

Микро-теория: зачем контроль версий даже «для одного».
Вопросы:

  1. Какие 2 риска снимают правила PR в вашей группе?
  2. Какие 2 метрики вы бы логировали для «мониторинга разработки»?

КП4 — Требования к UI + прототип GUI (пп. 8, 12) 

Сделать: docs/ui_requirements.md со сценариями, ASCII-макеты, «черновой» GUI.
Отчёт:

# КП4 — UI
## Сценарии
- Поиск книги: ...
- Выдача: ...
- Возврат: ...
## Экраны (словами)
(какие поля/кнопки, зачем)
## Пример диалога
(пошагово, без скринов)

Микро-теория: 2 правила UX, которые применили, и почему.
Вопросы:

  1. Что ухудшится в UX, если объединить выдачу и возврат в одну форму?
  2. Почему в вашем UI нельзя скрывать ошибку «нет доступных экземпляров»?

КП5 — Стиль кода + базовые алгоритмы (пп. 9–10) 

Сделать: docs/coding_style.md, реализация поиска/фильтра/расчёта штрафа/генератора номера.
Отчёт:

# КП5 — Стиль/алгоритмы
## 5 правил стиля с примерами "до/после"
(код-блоки на 3–6 строк)
## Поисковые запросы (примеры)
(текстом)
## Формула штрафа
(слова + пример расчёта)

Микро-теория: почему единый стиль ускоряет чтение кода.
Вопросы:

  1. Какая деталь вашего стиля уменьшает шанс на баг и почему?
  2. Почему генератор случайного кода лучше делать детерминируемым в тестах?

КП6 — Файловый ввод-вывод (пп. 15, 17) 

Сделать: реализация чтения/записи CSV/JSON, инварианты форматов.
Отчёт:

# КП6 — Файлы
## Форматы
(таблица полей CSV/JSON; типы; инварианты)
## Примеры записей
(код-блоки)
## Ошибки/исключения
(какие ловим, какой текст показываем)

Микро-теория: почему JSON повреждается и как это заметить заранее.
Вопросы:

  1. Что именно в ваших инвариантах предотвращает «полу-битые» строки?
  2. Как восстановиться, если пропала доступность файла во время записи?

КП7 — Сервер/клиент, формат сообщений (пп. 11, 16) 

Сделать: 3 эндпоинта (/books/issue/return), контракт JSON, коды ошибок; GUI реально ходит к API.
Отчёт:

# КП7 — API
## Контракты
/endpoint → вход/выход (список полей)
/issue → {...} → {...}
/return → {...} → {...}
## Отказоустойчивость
(какие ошибки и тексты сообщений)

Микро-теория: почему JSON удобнее CSV для API.
Вопросы:

  1. Какие 2 ошибки транспорта вы смоделировали и чем их закрыли?
  2. Как повлияло бы добавление пагинации на ваш GUI?

КП8 — Интеграция модулей (п. 14) 

Сделать: связать GUI↔API↔storage; событие BOOK_OVERDUE (текстовое уведомление).
Отчёт:

# КП8 — Интеграция
## Схема (словами)
GUI → API → storage → файлы → API → GUI
## Событие и обработчик
(короткий код)
## 2 интеграционных теста (псевдокод)
(шаги и ожидаемый результат)

Микро-теория: уровни интеграции и где у вас «стыки».
Вопросы:

  1. Где вы поставили «границу» между модульными и интеграционными тестами и почему?
  2. Какое место интеграции уязвимее в вашей схеме и почему?

КП9 — Отладка и исключения (пп. 13, 18) 

Сделать: централизованный перехват исключений, логирование, «отладочный миксин», 3 смоделированные ошибки.
Отчёт:

# КП9 — Отладка
## Где и почему перехватываем
(сервер/GUI; аргументы)
## Формат лог-строки
(код-блок)
## 3 смоделированные ошибки
(входные данные + ожидаемый текст)

Микро-теория: «не проглатывайте» какие-то исключения — какие и почему.
Вопросы:

  1. Объясните по 3 строкам реального trace, что там происходило.
  2. Какая ошибка должна «валить» приложение, а какая — только показывать сообщение?

КП10 — Рекомендации + спецификация настроек (п. 19; и 15 частично) 

Сделать: модуль recommender.pydocs/settings_spec.md — таблица всех настраиваемых параметров (пути файлов, лимит дней, штраф/день, пороги рекомендаций, флаги debug).
Отчёт:

# КП10 — Рекомендации/настройки
## Правила рекомендаций
(словами + пример вход/выход)
## Таблица настроек
| Ключ | Тип | Диапазон | По умолчанию | Что меняется |
## Эффект изменений
(3 примера влияния)

Микро-теория: что выносить в конфиг, а что оставлять в коде.
Вопросы:

  1. Почему именно эти настройки стоит вынести в конфиг в вашем проекте?
  2. Как убедиться, что рекомендации не «тормозят» при росте данных?

Частые ошибки и как их не допустить

  • Пустые файлы CSV/JSON ломают парсинг → в io_utils уже предусмотрен безопасный парсинг.
  • Смешение слоёв (GUI лезет в файлы) → всегда через API.
  • Нет сообщений об ошибках → пользователь должен получать понятный текст (покажите в отчёте).
  • PR без отчёта → отклонять. Без reports/CP<N>.md КП не засчитывается.
  • Скриншоты → запрещены.

Чек-лист качества перед сдачей

  • Проект запускается с нуля по шагам из README.md.
  • GUI работает: поиск/выдача/возврат, тексты ошибок понятные.
  • Файлы обновляются после операций.
  • Логи пишутся, есть 3 смоделированные ошибки.
  • Для каждой КП есть отчёт (Markdown), ответы на 2 вопроса.

Финальный запуск (памятка)

  1. Терминал A:
source .venv/bin/activate
pip install -r requirements.txt
python src/server/api.py
  1. Терминал B:
source .venv/bin/activate
python src/client_gui/app.py
  1. В 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
Вход: интервью, учебный план
Методы: контент-анализ; сценарный анализ
Риски: неполнота требований; меры: итерации раз/неделю

Проверочные вопросы:

  1. Какой этап жизненного цикла будет “бутылочным горлышком” именно в вашей ИС и почему?
  2. Какая одна метрика покажет, что «анализ» провалился, даже если разработка идет?

A2 — Модели ИС и SOA-вариант

Что сдать (reports/CPA2.md):

  • Сравнение 3 моделей построения (напр., монолит/клиент-сервер/SOA для нашей задачи) — таблицей критериев.
  • SOA-набросок: какие сервисы были бы выделены (каталог, учёт выдач, рекомендации), их контракты (словами).

Таблица примера (сокращённо):

| Критерий           | Монолит | Кл-сервер (наш) | SOA (вариант) |
| Сложность внедр.   | низкая  | средняя         | высокая       |
| Эволюция модулей   | низкая  | средняя         | высокая       |
| Нагрузка           | низкая  | средняя         | высокая       |

Вопросы:

  1. Если завтра добавится «мобильная выдача», какая модель дешевле в сопровождении — и почему именно для ваших модулей?
  2. Какой один контракт в SOA вы бы сделали идемпотентным — и зачем?

A3 — CASE-моделирование и IDEF0

Что сдать (reports/CPA3.md):

  • Контекстная IDEF0-диаграмма A-0 (ASCII) с целью/точкой зрения.
  • Декомпозиция на A0 → A1..A3 с перечислением работ и стрелок (вход/управление/механизм/выход — текстом).
  • Коротко — слияние/расщепление моделей, нумерация, «туннелирование» стрелок (если применимо) — словесно, с упоминанием где и зачем.

Пример ASCII фрагмента:

[ A0 Управление библиотекой ]
I: Запросы читателя
C: Правила выдачи
M: Библиотекарь, Сервер API
O: Выданные/возвращённые книги

Вопросы:

  1. Какое одно управление (Control) в вашей A0 критично перепутают с входом (Input) и как это повлияет на модель?
  2. Где «туннелирование» улучшит читаемость именно вашей схемы?

A4 — Состав обеспечения и экспертные/реального времени

Что сдать (reports/CPA4.md):

  • Таблица информационного/программного/технического обеспечения (что уже есть и что нужно).
  • Кратко — чем «экспертный модуль рекомендаций» отличается от полноценной экспертной системы (на вашем примере).
  • Почему ваша ИС не система реального времени и что пришлось бы изменить, чтобы ей стать.

Вопросы:

  1. Какой элемент программного обеспечения у вас — «точка наибольшего изменения» и почему?
  2. Если добавить «реальное время» (жесткие дедлайны отклика), какое решение станет первым узким местом?

A5 — Экономическая эффективность и выбор средств

Что сдать (reports/CPA5.md):

  • Стоимостная оценка по стадиям (оценка порядка величины → концептуальная → предварительная → окончательная → контрольная) — текст и Markdown-таблицы.
  • Обоснование выбора текущих средств проектирования (почему без БД, почему Flask/Tk) в ценовом выражении: трудозатраты, риски, стоимость смены технологии.

Вопросы:

  1. Какой один риск дороже всего в вашей оценке и почему вы его “купили”?
  2. Какие 2 метрики вы будете мониторить для пост-фактум сверки с оценкой?

A6 — Качество, безопасность, реинжиниринг

Что сдать (reports/CPA6.md):

  • Модель управления качеством для вашего учебного проекта: цели качества, артефакты контроля, точки измерений (входных/выходных), критерии приёмки.
  • Требования безопасности: роли/права (хотя бы текстом), защита данных файлов, обработка ошибок без утечек, базовые меры.
  • Реинжиниринг процесса: выберите один подпроцесс (например, выдача), предложите 2 способа улучшения (горизонтальное/вертикальное «сжатие») и ожидаемый эффект.

Вопросы:

  1. Какие 2 ошибки качества у вас наиболее вероятны именно из-за учебного формата?
  2. Какая одна мера безопасности даёт наибольший выигрыш при минимальной цене для вашей ИС?

A7 — Документация по ЕСПД/ЕСКД и ТЗ

Что сдать (reports/CPA7.md):

  • Перечень и комплектность документации, которую вы реально сможете поддерживать в учебном проекте (списком, с назначением).
  • Черновик ТЗ (разделы и краткое содержание: назначение, функции, интерфейсы, ограничения, проверки, критерии приёмки, стадии и источники данных).
  • Сетевой график (ASCII) ключевых вех/зависимостей — без картинок.

Вопросы:

  1. Какой один раздел ТЗ у вас будет «живым» (обновляемым) и почему?
  2. Где сетевой график “врёт”, но полезен — применительно к вашему графику?

A8 — Пользовательская и инсталляционная доки, самодок-код

Что сдать (reports/CPA8.md):

  • Руководство по инсталляции: пошагово (команды/варианты ОС/типичные ошибки).
  • Руководство пользователя: 3 сценария (поиск/выдача/возврат) — линейные чек-листы.
  • Пример «самодокументирующегося» кода: выделить 3 фрагмента и показать, какие имена/структура снимают необходимость комментариев.

Вопросы:

  1. Какая одна неоднозначность в терминах пользователя может “сломать” поддержку?
  2. Какой фрагмент кода вы упростили так, что комментарий стал не нужен — и почему?
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″]