Init
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
/config.yaml
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
TASK ?= ""
|
||||||
|
|
||||||
|
plan:
|
||||||
|
@codex "$$(cat agents/planner.md)"
|
||||||
|
|
||||||
|
implement:
|
||||||
|
@codex "$$(cat agents/implementer.md) $(TASK)"
|
||||||
|
|
||||||
|
review:
|
||||||
|
@codex "$$(cat agents/reviewer.md) $(TASK)"
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
You are Implementer.
|
||||||
|
|
||||||
|
You implement EXACTLY ONE task from spec/tasks/.
|
||||||
|
|
||||||
|
Before starting, you MUST read:
|
||||||
|
- spec/overview.md
|
||||||
|
- the assigned task file in spec/tasks/
|
||||||
|
|
||||||
|
You MUST NOT modify any files in spec/.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Your responsibilities
|
||||||
|
|
||||||
|
- Implement the task exactly as specified
|
||||||
|
- Place business rules in Business Logic
|
||||||
|
- Place orchestration in Service
|
||||||
|
- Respect all constraints from the task
|
||||||
|
- Add or update required tests
|
||||||
|
- Keep changes strictly within task scope
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mandatory verification steps (BEFORE finishing)
|
||||||
|
|
||||||
|
You MUST:
|
||||||
|
1. Verify all Definition of Done items from the task
|
||||||
|
2. Ensure Business Logic is fully unit-tested WITHOUT mocks
|
||||||
|
3. Ensure Service tests (if any) may use mocks/stubs
|
||||||
|
4. Run all tests specified in the task
|
||||||
|
5. Run all linters / type checks specified in the task
|
||||||
|
6. Verify no unrelated files were modified
|
||||||
|
7. Stage all intended changes:
|
||||||
|
- run: git add -A
|
||||||
|
- DO NOT commit
|
||||||
|
|
||||||
|
If ANY item fails, you MUST report it explicitly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output format (MANDATORY)
|
||||||
|
|
||||||
|
Output EXACTLY the following sections.
|
||||||
|
|
||||||
|
## Task
|
||||||
|
- Task ID and title
|
||||||
|
|
||||||
|
## Changes made
|
||||||
|
- List of changed files
|
||||||
|
- Short description per file
|
||||||
|
- Explicit layer for each file (Controller / Service / Business Logic / Repository / Adapter / Tests)
|
||||||
|
|
||||||
|
## Definition of Done verification
|
||||||
|
- Checklist copied from the task
|
||||||
|
- Each item marked as PASSED or FAILED
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Tests added or updated
|
||||||
|
- Test type:
|
||||||
|
- unit (no mocks)
|
||||||
|
- integration
|
||||||
|
- other
|
||||||
|
- Results
|
||||||
|
|
||||||
|
## Commands executed
|
||||||
|
- Exact commands run
|
||||||
|
- Result (success/failure)
|
||||||
|
|
||||||
|
## Git staging
|
||||||
|
- Confirm: git add -A executed
|
||||||
|
- Confirm: no commits created
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Edge cases, trade-offs, or limitations
|
||||||
|
- If none, write "None"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hard rules
|
||||||
|
|
||||||
|
- No scope expansion
|
||||||
|
- No speculative refactoring
|
||||||
|
- No changes outside task scope
|
||||||
|
- No spec changes
|
||||||
|
- No business logic in Service or Adapter
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
You are Planner.
|
||||||
|
|
||||||
|
You analyze a programming task or specification and produce planning artifacts.
|
||||||
|
You do NOT write production code.
|
||||||
|
|
||||||
|
You MUST always read:
|
||||||
|
- spec/overview.md
|
||||||
|
- spec/index.md
|
||||||
|
- existing files in spec/tasks/
|
||||||
|
|
||||||
|
You are the ONLY role allowed to create or modify files in spec/tasks/.
|
||||||
|
Write tasks in russian except headings, frontmatter and terms
|
||||||
|
|
||||||
|
Follow AGENTS.md strictly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Your responsibilities
|
||||||
|
|
||||||
|
Depending on the input, you may:
|
||||||
|
- create one or more new task files in spec/tasks/
|
||||||
|
- update existing task files in spec/tasks/
|
||||||
|
|
||||||
|
For every change in spec/tasks/, you MUST:
|
||||||
|
- use the minimal task format defined below
|
||||||
|
- update spec/index.md via the generator workflow
|
||||||
|
|
||||||
|
You MUST NOT:
|
||||||
|
- modify any source code
|
||||||
|
- change files outside spec/
|
||||||
|
- introduce architectural decisions not requested
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Minimal task format (MANDATORY)
|
||||||
|
|
||||||
|
Every task file in spec/tasks/ MUST follow this structure.
|
||||||
|
|
||||||
|
### Front matter (required)
|
||||||
|
|
||||||
|
---
|
||||||
|
id: <numeric, zero-padded, unique>
|
||||||
|
title: <short, precise, action-oriented>
|
||||||
|
status: TODO
|
||||||
|
created: <YYYY-MM-DD>
|
||||||
|
---
|
||||||
|
|
||||||
|
### Body (required sections, minimal)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Why this task exists. Short and factual.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
What must be implemented or changed.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
Hard limits:
|
||||||
|
- architectural
|
||||||
|
- scope
|
||||||
|
- forbidden changes
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Bullet list
|
||||||
|
- Objective and testable
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
Checklist. Task is DONE only if ALL items are satisfied.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
What tests must exist or be updated.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
Exact commands to verify completion
|
||||||
|
(e.g. pytest, ruff, mypy, docker compose).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planner output rules
|
||||||
|
|
||||||
|
When creating or updating tasks, output MUST include:
|
||||||
|
|
||||||
|
### Tasks created
|
||||||
|
- List of new spec/tasks/*.md files with full content
|
||||||
|
|
||||||
|
### Tasks updated
|
||||||
|
- List of modified spec/tasks/*.md files with changes
|
||||||
|
|
||||||
|
### Index update
|
||||||
|
- Explicit instruction to run:
|
||||||
|
python3 spec/gen_spec_index.py
|
||||||
|
|
||||||
|
If no tasks are created or updated, explicitly state:
|
||||||
|
- "No task changes required"
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- No code blocks outside task files
|
||||||
|
- No speculative tasks
|
||||||
|
- No refactoring-only tasks
|
||||||
|
- No vague goals
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
You are Reviewer.
|
||||||
|
|
||||||
|
You review code changes produced by Implementer.
|
||||||
|
|
||||||
|
Before reviewing, you MUST read:
|
||||||
|
- spec/overview.md
|
||||||
|
- the assigned task file in spec/tasks/
|
||||||
|
|
||||||
|
You MUST review ONLY staged changes:
|
||||||
|
- git diff --staged
|
||||||
|
|
||||||
|
You MUST NOT modify source code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Review scope (MANDATORY)
|
||||||
|
|
||||||
|
You must verify:
|
||||||
|
|
||||||
|
1. Task compliance
|
||||||
|
- Implementation matches task requirements
|
||||||
|
- No scope expansion
|
||||||
|
|
||||||
|
2. Architecture compliance
|
||||||
|
- Correct layer separation
|
||||||
|
- Business Logic purity
|
||||||
|
- Proper Service orchestration
|
||||||
|
- No business decisions in Repository or Adapter
|
||||||
|
|
||||||
|
3. Testing strategy
|
||||||
|
- Business Logic has unit tests WITHOUT mocks
|
||||||
|
- Tests cover task Definition of Done
|
||||||
|
- No missing critical tests
|
||||||
|
|
||||||
|
4. Project compliance
|
||||||
|
- Matches constraints and invariants from spec/overview.md
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output format (MANDATORY)
|
||||||
|
|
||||||
|
Comments must be in russian language.
|
||||||
|
Each comment MUST include severity in brackets:
|
||||||
|
(CRITICAL), (MAJOR), (MINOR)
|
||||||
|
|
||||||
|
Output EXACTLY the following sections.
|
||||||
|
|
||||||
|
## Critical issues
|
||||||
|
- Must be fixed before acceptance
|
||||||
|
- Include rule violated (AGENTS.md / task / overview)
|
||||||
|
|
||||||
|
## Major issues
|
||||||
|
- Significant quality or design problems
|
||||||
|
- Strongly recommended fixes
|
||||||
|
|
||||||
|
## Minor issues
|
||||||
|
- Style, naming, readability
|
||||||
|
- Non-blocking
|
||||||
|
|
||||||
|
## Definition of Done compliance
|
||||||
|
- For each DoD item:
|
||||||
|
- PASS or FAIL
|
||||||
|
- Short justification
|
||||||
|
|
||||||
|
## Architecture compliance
|
||||||
|
Explicitly check and state:
|
||||||
|
- Business Logic is dependency-free
|
||||||
|
- No business rules in Service
|
||||||
|
- No IO / DB / external calls in Business Logic
|
||||||
|
- No business decisions in Repository or Adapter
|
||||||
|
|
||||||
|
List violations explicitly, or state:
|
||||||
|
- "No architecture violations found"
|
||||||
|
|
||||||
|
## Testing compliance
|
||||||
|
- Business Logic unit tests present: YES / NO
|
||||||
|
- Mocks used in Business Logic tests: YES / NO (must be NO)
|
||||||
|
- Coverage gaps (if any)
|
||||||
|
|
||||||
|
## Project-spec compliance
|
||||||
|
- Confirm or list violations of spec/overview.md
|
||||||
|
|
||||||
|
## Review input
|
||||||
|
- Confirm you reviewed: git diff --staged
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hard rules
|
||||||
|
|
||||||
|
- No code changes
|
||||||
|
- No new requirements
|
||||||
|
- No architectural redesign
|
||||||
|
- No speculative improvements
|
||||||
Generated
+1576
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "g2s-aggregator"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Your Name <you@example.com>"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.14"
|
||||||
|
fastapi = "^0.135.1"
|
||||||
|
uvicorn = {extras = ["standard"], version = "^0.41.0"}
|
||||||
|
httpx = "^0.28.1"
|
||||||
|
pydantic = "^2.12.5"
|
||||||
|
pydantic-settings = "^2.13.1"
|
||||||
|
aioredis = "^2.0.1"
|
||||||
|
structlog = "^25.5.0"
|
||||||
|
opentelemetry-api = "^1.40.0"
|
||||||
|
opentelemetry-sdk = "^1.40.0"
|
||||||
|
opentelemetry-exporter-otlp = "^1.40.0"
|
||||||
|
opentelemetry-instrumentation-fastapi = "^0.61b0"
|
||||||
|
opentelemetry-instrumentation-httpx = "^0.61b0"
|
||||||
|
pytest = "^9.0.2"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = ["."]
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate spec/index.md from spec/tasks/*.md
|
||||||
|
|
||||||
|
Assumptions:
|
||||||
|
- Each task file starts with YAML-like front matter:
|
||||||
|
|
||||||
|
---
|
||||||
|
id: 001
|
||||||
|
title: Add endpoint /reports/addons
|
||||||
|
status: TODO # TODO | DONE
|
||||||
|
created: 2026-02-01
|
||||||
|
---
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- status lives inside each task file (as requested).
|
||||||
|
- index.md is fully overwritten and marked as generated.
|
||||||
|
- No external deps (no PyYAML). Supports simple "key: value" scalars.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 spec/gen_spec_index.py
|
||||||
|
python3 spec/gen_spec_index.py --check # verify index is up-to-date (CI)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime as dt
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SPEC_DIR = ROOT / "spec"
|
||||||
|
TASKS_DIR = SPEC_DIR / "tasks"
|
||||||
|
INDEX_PATH = SPEC_DIR / "index.md"
|
||||||
|
|
||||||
|
ALLOWED_STATUS = {"TODO", "DONE"}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class TaskMeta:
|
||||||
|
file: Path
|
||||||
|
id: str
|
||||||
|
title: str
|
||||||
|
status: str
|
||||||
|
created: str # YYYY-MM-DD
|
||||||
|
|
||||||
|
|
||||||
|
FRONT_MATTER_RE = re.compile(r"^---\s*$")
|
||||||
|
|
||||||
|
|
||||||
|
def read_front_matter(md_path: Path) -> Tuple[Dict[str, str], int]:
|
||||||
|
"""
|
||||||
|
Returns (meta_dict, end_line_index_exclusive).
|
||||||
|
If no front matter, returns ({}, 0).
|
||||||
|
"""
|
||||||
|
text = md_path.read_text(encoding="utf-8")
|
||||||
|
lines = text.splitlines()
|
||||||
|
|
||||||
|
if not lines or not FRONT_MATTER_RE.match(lines[0]):
|
||||||
|
return {}, 0
|
||||||
|
|
||||||
|
meta: Dict[str, str] = {}
|
||||||
|
i = 1
|
||||||
|
while i < len(lines):
|
||||||
|
if FRONT_MATTER_RE.match(lines[i]):
|
||||||
|
return meta, i + 1
|
||||||
|
|
||||||
|
line = lines[i].strip()
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Very small YAML subset: key: value
|
||||||
|
if ":" not in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, val = line.split(":", 1)
|
||||||
|
key = key.strip()
|
||||||
|
val = val.strip()
|
||||||
|
|
||||||
|
# Strip optional quotes
|
||||||
|
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
|
||||||
|
val = val[1:-1]
|
||||||
|
|
||||||
|
meta[key] = val
|
||||||
|
|
||||||
|
# Started front matter but never closed
|
||||||
|
raise ValueError(f"{md_path}: front matter starts with '---' but missing closing '---'")
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_id(md_path: Path, meta: Dict[str, str]) -> str:
|
||||||
|
# Prefer explicit id, else derive from filename prefix like "001-something.md"
|
||||||
|
if "id" in meta and meta["id"].strip():
|
||||||
|
return meta["id"].strip()
|
||||||
|
|
||||||
|
m = re.match(r"^(\d+)", md_path.stem)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
|
||||||
|
raise ValueError(f"{md_path}: missing 'id' in front matter and filename doesn't start with digits")
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_title(md_path: Path, meta: Dict[str, str], content_start_line: int) -> str:
|
||||||
|
if "title" in meta and meta["title"].strip():
|
||||||
|
return meta["title"].strip()
|
||||||
|
|
||||||
|
# Fallback: first markdown heading after front matter: "# Title"
|
||||||
|
text = md_path.read_text(encoding="utf-8")
|
||||||
|
lines = text.splitlines()
|
||||||
|
for line in lines[content_start_line:]:
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith("# "):
|
||||||
|
return line[2:].strip()
|
||||||
|
|
||||||
|
raise ValueError(f"{md_path}: missing 'title' in front matter and no '# ' heading found")
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_status(md_path: Path, meta: Dict[str, str]) -> str:
|
||||||
|
status = meta.get("status", "").strip().upper()
|
||||||
|
if status not in ALLOWED_STATUS:
|
||||||
|
raise ValueError(f"{md_path}: invalid or missing 'status' (must be one of: {sorted(ALLOWED_STATUS)})")
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_created(md_path: Path, meta: Dict[str, str]) -> str:
|
||||||
|
created = meta.get("created", "").strip()
|
||||||
|
if created:
|
||||||
|
# Validate YYYY-MM-DD
|
||||||
|
try:
|
||||||
|
dt.date.fromisoformat(created)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(f"{md_path}: invalid 'created' date '{created}' (expected YYYY-MM-DD)")
|
||||||
|
return created
|
||||||
|
|
||||||
|
# Fallback: file mtime (not perfect, but better than failing)
|
||||||
|
mtime = dt.datetime.fromtimestamp(md_path.stat().st_mtime, tz=dt.timezone.utc).date().isoformat()
|
||||||
|
return mtime
|
||||||
|
|
||||||
|
|
||||||
|
def load_tasks() -> List[TaskMeta]:
|
||||||
|
if not TASKS_DIR.exists():
|
||||||
|
raise FileNotFoundError(f"Tasks directory not found: {TASKS_DIR}")
|
||||||
|
|
||||||
|
md_files = sorted(TASKS_DIR.glob("*.md"))
|
||||||
|
tasks: List[TaskMeta] = []
|
||||||
|
seen_ids: Dict[str, Path] = {}
|
||||||
|
|
||||||
|
for f in md_files:
|
||||||
|
meta, content_start = read_front_matter(f)
|
||||||
|
|
||||||
|
task_id = normalize_id(f, meta)
|
||||||
|
if task_id in seen_ids:
|
||||||
|
raise ValueError(f"Duplicate task id '{task_id}': {seen_ids[task_id]} and {f}")
|
||||||
|
seen_ids[task_id] = f
|
||||||
|
|
||||||
|
title = normalize_title(f, meta, content_start)
|
||||||
|
status = normalize_status(f, meta)
|
||||||
|
created = normalize_created(f, meta)
|
||||||
|
|
||||||
|
tasks.append(TaskMeta(file=f, id=task_id, title=title, status=status, created=created))
|
||||||
|
|
||||||
|
# Sort by numeric id if possible
|
||||||
|
def sort_key(t: TaskMeta):
|
||||||
|
try:
|
||||||
|
return (0, int(t.id))
|
||||||
|
except ValueError:
|
||||||
|
return (1, t.id)
|
||||||
|
|
||||||
|
return sorted(tasks, key=sort_key)
|
||||||
|
|
||||||
|
|
||||||
|
def render_index(tasks: List[TaskMeta]) -> str:
|
||||||
|
generated_at = dt.datetime.now(tz=dt.timezone.utc).replace(microsecond=0).isoformat()
|
||||||
|
|
||||||
|
lines: List[str] = []
|
||||||
|
lines.append("# Spec Tasks Index")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("> ⚠️ This file is generated. Do not edit manually.")
|
||||||
|
lines.append(f"> Generated at (UTC): `{generated_at}`")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("## Tasks")
|
||||||
|
lines.append("")
|
||||||
|
lines.append("| ID | Status | Created | Title | File |")
|
||||||
|
lines.append("|---:|:------:|:-------:|:------|:-----|")
|
||||||
|
|
||||||
|
for t in tasks:
|
||||||
|
rel = t.file.relative_to(ROOT).as_posix()
|
||||||
|
title = t.title.replace("|", "\\|")
|
||||||
|
lines.append(f"| {t.id} | {t.status} | {t.created} | {title} | `{rel}` |")
|
||||||
|
|
||||||
|
lines.append("")
|
||||||
|
lines.append("## Summary")
|
||||||
|
lines.append("")
|
||||||
|
todo = sum(1 for t in tasks if t.status == "TODO")
|
||||||
|
done = sum(1 for t in tasks if t.status == "DONE")
|
||||||
|
lines.append(f"- Total: **{len(tasks)}**")
|
||||||
|
lines.append(f"- TODO: **{todo}**")
|
||||||
|
lines.append(f"- DONE: **{done}**")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--check", action="store_true", help="fail if spec/index.md is not up-to-date")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
tasks = load_tasks()
|
||||||
|
content = render_index(tasks)
|
||||||
|
|
||||||
|
if args.check:
|
||||||
|
existing = INDEX_PATH.read_text(encoding="utf-8") if INDEX_PATH.exists() else ""
|
||||||
|
if existing != content:
|
||||||
|
print("spec/index.md is not up-to-date. Run: python3 spec/gen_spec_index.py", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
return 0
|
||||||
|
|
||||||
|
SPEC_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
INDEX_PATH.write_text(content, encoding="utf-8")
|
||||||
|
print(f"Wrote {INDEX_PATH.relative_to(ROOT)} ({len(tasks)} tasks)")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Spec Tasks Index
|
||||||
|
|
||||||
|
> ⚠️ This file is generated. Do not edit manually.
|
||||||
|
> Generated at (UTC): `2026-03-07T09:07:45+00:00`
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
| ID | Status | Created | Title | File |
|
||||||
|
|---:|:------:|:-------:|:------|:-----|
|
||||||
|
| 000 | DONE | 2026-02-01 | Task format reference | `spec/tasks/000_template.md` |
|
||||||
|
| 001 | TODO | 2026-03-07 | Create app skeleton and component configuration | `spec/tasks/001_create_app_skeleton_and_config.md` |
|
||||||
|
| 002 | TODO | 2026-03-07 | Implement pure domain quote rules | `spec/tasks/002_implement_pure_domain_quote_rules.md` |
|
||||||
|
| 003 | TODO | 2026-03-07 | Add CDEK provider adapter | `spec/tasks/003_add_cdek_provider_adapter.md` |
|
||||||
|
| 004 | TODO | 2026-03-07 | Add Redis price cache repository | `spec/tasks/004_add_redis_price_cache_repository.md` |
|
||||||
|
| 005 | TODO | 2026-03-07 | Implement aggregator service workflow | `spec/tasks/005_implement_aggregator_service_workflow.md` |
|
||||||
|
| 006 | TODO | 2026-03-07 | Add delivery price controller endpoint | `spec/tasks/006_add_delivery_price_controller.md` |
|
||||||
|
| 007 | TODO | 2026-03-07 | Add observability correlation and telemetry | `spec/tasks/007_add_observability_correlation.md` |
|
||||||
|
| 008 | TODO | 2026-03-07 | Add SigNoz to Telegram alerting configuration | `spec/tasks/008_add_signoz_telegram_alerting.md` |
|
||||||
|
| 009 | TODO | 2026-03-07 | Add local infrastructure stack | `spec/tasks/009_add_local_infra_stack.md` |
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- Total: **10**
|
||||||
|
- TODO: **9**
|
||||||
|
- DONE: **1**
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
# spec/overview.md
|
||||||
|
|
||||||
|
Контекст, специфичный для проекта агрегатора служб доставки.
|
||||||
|
Глобальные правила определены в AGENTS.md.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Проект
|
||||||
|
|
||||||
|
**Название:** Агрегатор служб доставки (бэкенд)
|
||||||
|
**Язык:** Python 3.14
|
||||||
|
**Назначение:** Агрегировать расчёты стоимости доставки от нескольких служб и отдавать единый ответ фронтенду через унифицированный API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Продуктовые требования
|
||||||
|
|
||||||
|
- Принимать запрос на расчёт стоимости доставки (откуда, куда, вес, габариты)
|
||||||
|
- Опрашивать всех зарегистрированных провайдеров параллельно
|
||||||
|
- Возвращать унифицированный список тарифов, отсортированных по цене
|
||||||
|
- Если провайдер вернул ошибку или не ответил вовремя — исключить его из результата, не падая целиком
|
||||||
|
- Кешировать ответы провайдеров для исключения повторных внешних запросов. Время кэширования вынести в конфиг
|
||||||
|
- Обеспечивать структурированную наблюдаемость: трейсы, метрики, логи — связанные по request ID
|
||||||
|
- Отправлять алерты в Telegram при аномалиях (ошибки, всплески задержки, недоступность провайдера). token вынести в конфиг
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Провайдеры
|
||||||
|
|
||||||
|
### Фаза 1
|
||||||
|
- **CDEK** — https://apidoc.cdek.ru/
|
||||||
|
- Аутентификация: OAuth2 (client credentials)
|
||||||
|
- Операции: расчёт тарифа
|
||||||
|
- Cache TTL: 15 минут
|
||||||
|
|
||||||
|
### Фаза 2+
|
||||||
|
- Дополнительные провайдеры (Boxberry, DHL и др.) — подключаются через интерфейс `DeliveryProvider` без изменений в логике агрегации
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Компоненты
|
||||||
|
|
||||||
|
### Controller (`app/controllers/v1/delivery.py`)
|
||||||
|
- `POST /api/v1/delivery/price` — принимает `DeliveryRequest`, возвращает `list[DeliveryPrice]`
|
||||||
|
- Парсинг и валидация входных данных через Pydantic
|
||||||
|
- Маппинг исключений сервиса в HTTP-ответы
|
||||||
|
- Вызывает ровно один метод Service: `AggregatorService.get_all_prices()`
|
||||||
|
|
||||||
|
### Service (`app/services/aggregator.py`)
|
||||||
|
- `AggregatorService.get_all_prices(request: DeliveryRequest) -> list[DeliveryPrice]`
|
||||||
|
- Распределяет запросы по всем зарегистрированным провайдерам через `asyncio.gather(..., return_exceptions=True)`
|
||||||
|
- Фильтрует упавшие результаты
|
||||||
|
- Сортирует тарифы по цене
|
||||||
|
- Не содержит бизнес-логики и логики, специфичной для провайдеров
|
||||||
|
|
||||||
|
### Business Logic (`app/domain/`)
|
||||||
|
- Правила сортировки и фильтрации тарифов
|
||||||
|
- Логика сравнения цен
|
||||||
|
- Нормализация входных данных (например, правила округления веса)
|
||||||
|
- Чистые функции, без IO, без зависимостей от фреймворков
|
||||||
|
|
||||||
|
### Repository (`app/repositories/cache/`)
|
||||||
|
- `PriceCache` — хранилище ключ/значение на базе Redis
|
||||||
|
- Операции: `get(key)`, `set(key, value, ttl)`, `invalidate(key)`
|
||||||
|
- Без бизнес-решений; формирование ключа — ответственность Adapter
|
||||||
|
|
||||||
|
### Adapter (`app/adapters/delivery_providers`)
|
||||||
|
- `base.py` — абстрактный интерфейс `DeliveryProvider`:
|
||||||
|
```python
|
||||||
|
class DeliveryProvider(ABC):
|
||||||
|
name: str
|
||||||
|
async def get_price(self, request: DeliveryRequest) -> DeliveryPrice: ...
|
||||||
|
```
|
||||||
|
- `cdek/client.py` — HTTP-клиент (httpx AsyncClient), аутентификация, ретраи, таймаут (10с)
|
||||||
|
- `cdek/auth.py` — управление OAuth2-токеном
|
||||||
|
- `cdek/mapper.py` — ответ CDEK → `DeliveryPrice`
|
||||||
|
- Каждый адаптер владеет своей конфигурацией; наружу экспонирует только `get_price()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Модели данных
|
||||||
|
|
||||||
|
### Входная: `DeliveryRequest`
|
||||||
|
```
|
||||||
|
entity: Enum(individual, legal)
|
||||||
|
from_city: str
|
||||||
|
to_city: str
|
||||||
|
weight_kg: float
|
||||||
|
length_cm: float
|
||||||
|
width_cm: float
|
||||||
|
height_cm: float
|
||||||
|
```
|
||||||
|
|
||||||
|
### Выходная: `DeliveryPrice`
|
||||||
|
```
|
||||||
|
provider: str
|
||||||
|
service_name: str
|
||||||
|
price: Decimal
|
||||||
|
currency: str
|
||||||
|
delivery_days_min: int
|
||||||
|
delivery_days_max: int
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Технологический стек
|
||||||
|
|
||||||
|
| Задача | Инструмент |
|
||||||
|
|---|---|
|
||||||
|
| Web-фреймворк | FastAPI |
|
||||||
|
| HTTP-клиент | httpx (async) |
|
||||||
|
| Валидация | Pydantic v2 |
|
||||||
|
| Кеш | Redis |
|
||||||
|
| Конфигурация | pydantic-settings |
|
||||||
|
| Сервер | Uvicorn |
|
||||||
|
| Инструментация | OpenTelemetry SDK |
|
||||||
|
| Бэкенд наблюдаемости | SigNoz (self-hosted) |
|
||||||
|
| Структурные логи | structlog |
|
||||||
|
| Алерты | SigNoz Alert Rules → Webhook → Telegram Bot API |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Наблюдаемость
|
||||||
|
|
||||||
|
- Каждому запросу присваивается `request_id` (UUID) через middleware
|
||||||
|
- `request_id` привязывается ко всем логам через `structlog.contextvars`
|
||||||
|
- Трейсы отправляются через OpenTelemetry; FastAPI и httpx инструментируются автоматически
|
||||||
|
- Ручные спаны оборачивают: `AggregatorService.get_all_prices`, `get_price` каждого провайдера, обращения к кешу
|
||||||
|
- Атрибуты спанов: `provider`, `from_city`, `to_city`, `weight_kg`, `cache_hit`, `tariffs_found`
|
||||||
|
- Отслеживаемые метрики: количество запросов, количество ошибок, время ответа (p50/p99), доступность каждого провайдера
|
||||||
|
- Все три сигнала (трейсы, метрики, логи) связаны через `trace_id`
|
||||||
|
|
||||||
|
### Условия алертов (Telegram)
|
||||||
|
- Провайдер вернул 5xx — порог: 5 ошибок за 5 минут
|
||||||
|
- p99 времени ответа провайдера > 5000ms в течение 10 минут
|
||||||
|
- Провайдер недоступен более 5 минут
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
app/
|
||||||
|
├── controllers/
|
||||||
|
│ ├── http_client.py
|
||||||
|
│ ├── middleware.py
|
||||||
|
│ └── v1/
|
||||||
|
│ └── delivery.py # Controller
|
||||||
|
├── services/
|
||||||
|
│ └── aggregator.py # Service
|
||||||
|
├── domain/
|
||||||
|
│ └── quotes.py # Business Logic
|
||||||
|
├── adapters/
|
||||||
|
│ └── delivery_providers/
|
||||||
|
│ ├── base.py # Интерфейс Adapter
|
||||||
|
│ └── cdek/
|
||||||
|
│ ├── client.py
|
||||||
|
│ ├── auth.py
|
||||||
|
│ └── mapper.py
|
||||||
|
├── repositories/
|
||||||
|
│ └── cache/
|
||||||
|
│ └── redis_cache.py # Repository
|
||||||
|
├── schemas/
|
||||||
|
│ ├── request.py
|
||||||
|
│ └── response.py
|
||||||
|
└── config.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Инфраструктура
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose сервисы
|
||||||
|
app # FastAPI-приложение
|
||||||
|
redis # Кеш тарифов
|
||||||
|
signoz # Бэкенд наблюдаемости (трейсы + метрики + логи + алерты)
|
||||||
|
```
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
id: 000
|
||||||
|
title: Task format reference
|
||||||
|
status: DONE
|
||||||
|
created: 2026-02-01
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
В репозитории хранится эталонная задача, которая показывает обязательный формат задач.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Дать каноничный пример минимального формата задачи, обязательного для всех файлов в `spec/tasks/`.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Содержать только требования к формату, без продуктовой реализации.
|
||||||
|
- Оставаться совместимой с обязательной структурой задач Planner.
|
||||||
|
- Не добавлять требований к реализации.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Front matter содержит `id`, `title`, `status`, `created`.
|
||||||
|
- Все обязательные секции присутствуют в требуемом порядке.
|
||||||
|
- Содержимое остаётся эталонной, неисполняемой задачей.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [x] Front matter использует обязательные поля.
|
||||||
|
- [x] Обязательные секции присутствуют.
|
||||||
|
- [x] Файл пригоден только как reference формата.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Runtime-тесты не требуются.
|
||||||
|
- Формат валидируется успешной генерацией spec index.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `python3 spec/gen_spec_index.py --check`
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
id: 001
|
||||||
|
title: Create app skeleton and component configuration
|
||||||
|
status: DONE
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Сейчас в репозитории есть только specification-файлы, при этом `spec/overview.md` задаёт конкретную структуру приложения и требования к конфигурации компонентов.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Создать начальный `app/` skeleton и модели конфигурации из project overview, включая отдельные секции конфигурации для Controller, Service, Business Logic, Repository, Adapter, Observability и Alerts.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Соблюдать layered architecture из `AGENTS.md`.
|
||||||
|
- Scope задачи: только scaffolding и configuration, без business workflows.
|
||||||
|
- Не реализовывать provider HTTP calls, cache behavior или aggregation logic в рамках этой задачи.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Структура `app/` создана согласно `spec/overview.md`.
|
||||||
|
- `app/config.py` содержит отдельные component configuration sections. Config must be a .yaml-file added to .gitignore.
|
||||||
|
- FastAPI entrypoint существует и успешно импортирует configuration.
|
||||||
|
- В файлах Controller, Service, Repository и Adapter отсутствуют business rules.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Созданы обязательные директории `app/` и базовые файлы.
|
||||||
|
- [ ] Реализованы раздельные секции конфигурации по компонентам.
|
||||||
|
- [ ] Проходит app import smoke test.
|
||||||
|
- [ ] Изменения остаются строго в scope задачи.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить/import smoke test для FastAPI app startup.
|
||||||
|
- Добавить unit tests, проверяющие загрузку configuration sections из environment variables.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/smoke/test_app_import.py -q`
|
||||||
|
- `poetry run pytest tests/config/test_config_sections.py -q`
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
id: 002
|
||||||
|
title: Implement pure domain quote rules
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
`spec/overview.md` требует business rules для фильтрации тарифов, сравнения цен, сортировки и нормализации входных данных в чистом слое Business Logic.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Реализовать детерминированные, dependency-free domain functions в `app/domain/quotes.py` для нормализации запроса и фильтрации/упорядочивания котировок.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Business Logic должна быть pure и dependency-free.
|
||||||
|
- Запрещены прямые IO, adapters, repositories, framework imports и внешние вызовы.
|
||||||
|
- Слои Service и Controller не должны забирать domain rules из этой задачи.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Domain functions нормализуют значения запроса по явным детерминированным правилам.
|
||||||
|
- Domain functions отфильтровывают невалидные котировки по определённым domain conditions.
|
||||||
|
- Domain functions сортируют валидные котировки по возрастанию цены.
|
||||||
|
- Логика реализована только в `app/domain/` и доступна для вызова из Service.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Реализованы pure domain functions для normalization/filtering/sorting.
|
||||||
|
- [ ] Поведение функций детерминированное и без side effects.
|
||||||
|
- [ ] В domain module нет импортов не-domain зависимостей.
|
||||||
|
- [ ] Unit tests (без mocks) покрывают стандартные и edge cases.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить `tests/domain/test_quotes.py` только с unit tests.
|
||||||
|
- Покрыть edge cases: пустой input, невалидные quotes, одинаковые цены, границы нормализации.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/domain/test_quotes.py -q`
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
id: 003
|
||||||
|
title: Add CDEK provider adapter
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Phase 1 в `spec/overview.md` требует поддержку provider CDEK с OAuth2 authentication и маппингом расчёта тарифа.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Реализовать adapter interfaces и модули CDEK adapter: provider contract, OAuth2 token handling, HTTP client behavior и преобразование ответа в `DeliveryPrice`.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Только Adapter layer; без business decisions и sorting logic.
|
||||||
|
- Внешний IO должен оставаться внутри adapter modules.
|
||||||
|
- Timeout для CDEK должен быть 10 секунд согласно specification.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Существует интерфейс `DeliveryProvider` со стабильным контрактом `get_price()`.
|
||||||
|
- Модуль CDEK auth получает OAuth2 token через client credentials и переиспользует валидный token до истечения.
|
||||||
|
- CDEK client выполняет tariff request с timeout и retry behavior.
|
||||||
|
- CDEK mapper преобразует provider response в унифицированную schema `DeliveryPrice`.
|
||||||
|
- Adapter наружу предоставляет только provider-facing API, необходимый Service.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Реализован base provider interface.
|
||||||
|
- [ ] Реализованы модули CDEK auth/client/mapper.
|
||||||
|
- [ ] CDEK adapter возвращает унифицированную price model при успешном ответе.
|
||||||
|
- [ ] Adapter tests покрывают auth refresh, timeout/retry и mapping cases.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить adapter unit tests для token lifecycle behavior.
|
||||||
|
- Добавить adapter tests для response mapping и HTTP error handling.
|
||||||
|
- Для внешних HTTP взаимодействий использовать stubs/mocks.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/adapters/delivery_providers/cdek -q`
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
id: 004
|
||||||
|
title: Add Redis price cache repository
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Product requirements требуют кеширование ответов provider с configurable TTL, а архитектура закрепляет доступ к данным за слоем Repository.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Реализовать `PriceCache` repository на Redis с операциями `get`, `set` и `invalidate`, включая поддержку TTL.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Только Repository layer: data access и persistence mapping.
|
||||||
|
- В repository code не допускаются business decisions и workflow orchestration.
|
||||||
|
- Генерация cache key не должна реализовываться в repository.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Repository module существует по пути `app/repositories/cache/redis_cache.py`.
|
||||||
|
- `PriceCache` предоставляет async методы `get`, `set`, `invalidate`.
|
||||||
|
- `set` применяет TTL из configuration.
|
||||||
|
- Serialization/deserialization cached payload выполняются детерминированно.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Реализован Redis-backed cache repository.
|
||||||
|
- [ ] Интерфейс repository соответствует требуемым операциям.
|
||||||
|
- [ ] Поведение TTL покрыто тестами.
|
||||||
|
- [ ] В repository отсутствует business logic.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить repository tests для cache hit/miss, set/get roundtrip, invalidate и TTL expiration.
|
||||||
|
- Использовать Redis test double или изолированный test Redis instance.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/repositories/cache/test_redis_cache.py -q`
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
id: 005
|
||||||
|
title: Implement aggregator service workflow
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
`spec/overview.md` определяет `AggregatorService.get_all_prices()` как application orchestrator для параллельных provider calls, graceful degradation, caching и отсортированного унифицированного результата.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Реализовать `AggregatorService.get_all_prices(request: DeliveryRequest) -> list[DeliveryPrice]` в `app/services/aggregator.py` с параллельным выполнением, cache coordination и делегированием business rules в domain functions.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Service выполняет только orchestration; pure business rules остаются в Business Logic.
|
||||||
|
- Использовать dependency injection для providers и repository.
|
||||||
|
- Для вызовов providers использовать `asyncio.gather(..., return_exceptions=True)`.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Service вызывает все зарегистрированные providers параллельно.
|
||||||
|
- Failures/timeouts отдельных providers не валят весь запрос; упавшие providers исключаются.
|
||||||
|
- Cache проверяется до внешнего provider request и обновляется после успешного provider response.
|
||||||
|
- Фильтрация/сортировка котировок делегируется domain functions.
|
||||||
|
- Возвращаемый список котировок унифицирован и отсортирован по цене по возрастанию.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Метод Service реализован с injected dependencies.
|
||||||
|
- [ ] Параллельная orchestration использует `return_exceptions=True`.
|
||||||
|
- [ ] Реализована cache coordination для provider responses.
|
||||||
|
- [ ] Service tests покрывают full-success, partial-failure, all-failure и cache-hit сценарии.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить service tests со stubs/mocks для adapters и repository.
|
||||||
|
- Проверить orchestration и исключение неуспешных providers.
|
||||||
|
- Проверить делегирование sorting/filtering в domain logic.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/services/test_aggregator.py -q`
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
id: 006
|
||||||
|
title: Add delivery price controller endpoint
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
API contract требует endpoint `POST /api/v1/delivery/price` с DTO validation и HTTP error mapping, при этом Controller должен вызывать ровно один метод Service.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Реализовать request/response schemas и endpoint в `app/controllers/v1/delivery.py`, который валидирует входные данные, делегирует в `AggregatorService.get_all_prices()` и маппит service exceptions в HTTP responses.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Controller не должен содержать business logic или provider-specific branching.
|
||||||
|
- На каждый запрос Controller должен вызывать ровно один метод Service.
|
||||||
|
- Validation должна использовать Pydantic models из schema layer.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- `POST /api/v1/delivery/price` принимает payload `DeliveryRequest` и возвращает `list[DeliveryPrice]`.
|
||||||
|
- Невалидный input возвращает validation error response.
|
||||||
|
- Controller делегирует обработку в `AggregatorService.get_all_prices()`.
|
||||||
|
- Service exceptions маппятся в детерминированные HTTP responses.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Реализованы request и response schemas.
|
||||||
|
- [ ] Controller endpoint подключён в FastAPI router.
|
||||||
|
- [ ] Path и HTTP method endpoint соответствуют specification.
|
||||||
|
- [ ] API tests покрывают успешный ответ, validation failure и mapped service error.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить controller tests для поведения route и делегирования в service.
|
||||||
|
- Добавить API-level tests для валидации request schema и response schema.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/controllers/v1/test_delivery.py -q`
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
id: 007
|
||||||
|
title: Add observability correlation and telemetry
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Проект требует связать traces, metrics и logs через request correlation (`request_id` и `trace_id`) и добавить manual spans вокруг ключевых операций.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Реализовать observability wiring: middleware для `request_id`, propagation structured logging context, OpenTelemetry instrumentation для FastAPI/httpx и manual spans для операций service/provider/cache.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Все observability concerns держать вне Business Logic.
|
||||||
|
- При добавлении telemetry не вводить business decision-making.
|
||||||
|
- Инструментировать только слои и операции, указанные в `spec/overview.md`.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Middleware назначает UUID `request_id` для каждого запроса.
|
||||||
|
- `request_id` добавляется в structured logs через contextvars.
|
||||||
|
- Включена OpenTelemetry instrumentation для FastAPI и httpx.
|
||||||
|
- Есть manual spans для `AggregatorService.get_all_prices`, provider `get_price` и cache operations.
|
||||||
|
- Span attributes включают `provider`, `from_city`, `to_city`, `weight_kg`, `cache_hit`, `tariffs_found` там, где применимо.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Реализован middleware корреляции запросов.
|
||||||
|
- [ ] В logging context есть binding `request_id`.
|
||||||
|
- [ ] Реализована автоматическая и manual tracing instrumentation.
|
||||||
|
- [ ] Observability tests проверяют создание spans и обязательные attributes.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить middleware tests для генерации и propagation `request_id`.
|
||||||
|
- Добавить telemetry tests с in-memory span exporter для проверки names/attributes.
|
||||||
|
- Добавить logging tests, проверяющие наличие `request_id` в log context.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/controllers/test_middleware_request_id.py -q`
|
||||||
|
- `poetry run pytest tests/observability/test_tracing.py -q`
|
||||||
|
- `poetry run pytest tests/observability/test_logging_context.py -q`
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
id: 008
|
||||||
|
title: Add SigNoz to Telegram alerting configuration
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
`spec/overview.md` требует anomaly alerting через цепочку `SigNoz Alert Rules -> Webhook -> Telegram Bot API` с вынесением token в configuration.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Добавить configuration и infrastructure artifacts, необходимые для маршрутизации anomaly alerts из SigNoz в Telegram и для соответствия alert thresholds требованиям проекта.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Alert transport/configuration должны быть отделены от business logic.
|
||||||
|
- Telegram credentials должны приходить из configuration и не быть hardcoded.
|
||||||
|
- Thresholds должны соответствовать alert conditions из `spec/overview.md`.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Configuration содержит отдельную секцию Alerts с Telegram settings.
|
||||||
|
- Infrastructure artifact(s) описывают routing от SigNoz alert webhook к Telegram Bot API.
|
||||||
|
- Представлены alert conditions для:
|
||||||
|
- provider 5xx: 5 ошибок за 5 минут
|
||||||
|
- provider p99 latency > 5000ms в течение 10 минут
|
||||||
|
- provider unavailable более 5 минут
|
||||||
|
- Setup instructions содержат обязательные environment variables и шаги верификации.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Реализована schema конфигурации Alerts.
|
||||||
|
- [ ] Добавлены artifact(s) для маршрутизации SigNoz-to-Telegram.
|
||||||
|
- [ ] Определены все три обязательных anomaly conditions.
|
||||||
|
- [ ] Инструкции валидации исполнимы в local environment.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить config tests для проверки загрузки alert settings и обязательных полей.
|
||||||
|
- Добавить tests для alert payload transformation/transport logic, если он реализован в коде.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `poetry run pytest tests/config/test_alerts_config.py -q`
|
||||||
|
- `docker compose config`
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
id: 009
|
||||||
|
title: Add local infrastructure stack
|
||||||
|
status: TODO
|
||||||
|
created: 2026-03-07
|
||||||
|
---
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Overview задаёт локальные infrastructure components `app`, `redis` и `signoz`, необходимые для runtime и observability.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Добавить Docker Compose infrastructure definitions для local development, включая application service, Redis cache и SigNoz observability backend.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Scope задачи: только infrastructure wiring.
|
||||||
|
- Не переносить business logic в infrastructure scripts.
|
||||||
|
- Имена services должны соответствовать overview: `app`, `redis`, `signoz`.
|
||||||
|
- Не изменять файлы в `spec/`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
- Compose file определяет services `app`, `redis`, `signoz`.
|
||||||
|
- Service `app` содержит environment wiring для cache и observability endpoints.
|
||||||
|
- Service `redis` достижим из `app` во внутренней сети.
|
||||||
|
- Service `signoz` достижим для telemetry export при локальном запуске.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
- [ ] Добавлен compose definition с обязательными services.
|
||||||
|
- [ ] Задокументированы environment variables для локальной интеграции.
|
||||||
|
- [ ] `docker compose config` проходит валидацию.
|
||||||
|
- [ ] Добавлены базовые startup instructions.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
- Добавить/обновить infrastructure checks для валидации compose syntax.
|
||||||
|
- Добавить smoke sequence команд для проверки локального запуска.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
- `docker compose config`
|
||||||
|
- `docker compose up -d redis signoz`
|
||||||
|
- `docker compose ps`
|
||||||
Reference in New Issue
Block a user