Compare commits
8
Commits
c9fe69db3a
...
eda91f7512
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eda91f7512 | ||
|
|
6ec2748b84 | ||
|
|
14c1bf437b | ||
|
|
18de84d1a9 | ||
|
|
1fd7395893 | ||
|
|
77135df803 | ||
|
|
50b9ffc1f1 | ||
|
|
8c29af7f90 |
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "1.0",
|
|
||||||
"timestamp": "2026-06-03T16:33:39Z",
|
|
||||||
"phase": "7",
|
|
||||||
"phase_name": "Redo and Optimize LLM Integration",
|
|
||||||
"phase_dir": ".planning/phases/07-redo-and-optimize-llm-integration",
|
|
||||||
"plan": 0,
|
|
||||||
"task": 0,
|
|
||||||
"total_tasks": 15,
|
|
||||||
"status": "paused",
|
|
||||||
"completed_tasks": [],
|
|
||||||
"remaining_tasks": [
|
|
||||||
{"id": "07-01", "name": "Foundation: migration 0005 + SystemSettings ORM + ai_config.py HKDF + Wave 0 stubs", "status": "not_started"},
|
|
||||||
{"id": "07-02", "name": "ProviderConfig + GenericOpenAIProvider + OpenAI singleton + registry get_provider + MAX_AI_CHARS removal", "status": "not_started"},
|
|
||||||
{"id": "07-03", "name": "Anthropic output_config + AnthropicProvider singleton + classifier wiring via load_provider_config", "status": "not_started"},
|
|
||||||
{"id": "07-04", "name": "Celery retry 30/90/270s + _ClassificationError + re-classify endpoint re-queues", "status": "not_started"},
|
|
||||||
{"id": "07-05", "name": "Admin AI Providers panel + DocumentCard badge + Re-analyze button + human UAT checkpoint", "status": "not_started"}
|
|
||||||
],
|
|
||||||
"blockers": [],
|
|
||||||
"human_actions_pending": [
|
|
||||||
{
|
|
||||||
"action": "UAT checkpoint in Plan 07-05 Task 4: manually test admin AI Providers panel and DocumentCard badge end-to-end",
|
|
||||||
"context": "Plan 05 is autonomous=false with a blocking human checkpoint — executor will pause and request manual verification",
|
|
||||||
"blocking": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"decisions": [
|
|
||||||
{"decision": "D-03: Anthropic uses output_config.format.type='json_schema' (not tool_use)", "rationale": "Constrained decoding, no beta headers needed, SDK >=0.95.0", "phase": "7"},
|
|
||||||
{"decision": "D-07: Client lifecycle = instance-level singleton self._client in __init__", "rationale": "AsyncOpenAI/AsyncAnthropic manage httpx connection pool; recreating per call destroys pool reuse", "phase": "7"},
|
|
||||||
{"decision": "D-14: extra_hosts already present in docker-compose.yml — no changes needed", "rationale": "RESEARCH.md confirmed both backend and celery-worker already have host.docker.internal:host-gateway", "phase": "7"},
|
|
||||||
{"decision": "D-02/Gemini: supports_json_mode=False, falls back to parse_classification()", "rationale": "Gemini OpenAI-compat does not support json_object string mode", "phase": "7"}
|
|
||||||
],
|
|
||||||
"uncommitted_files": [],
|
|
||||||
"next_action": "Run /gsd:execute-phase 7 after /clear — start with plan 07-01",
|
|
||||||
"context_notes": "Phase 7 planning 100% complete. All 5 plans created and verified (commit 3df6250). anthropic pin must be bumped to >=0.95.0 in Plan 02. POST /api/documents/{id}/classify already exists — Plan 04 changes behavior only. AdminAiConfigTab.vue per-user table must be preserved; Plan 05 adds global section ABOVE it."
|
|
||||||
}
|
|
||||||
+6
-6
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: Redo and Optimize LLM Integration
|
milestone_name: Redo and Optimize LLM Integration
|
||||||
current_phase: 7.1 (complete)
|
current_phase: 7.1 (complete)
|
||||||
status: complete
|
status: completed
|
||||||
last_updated: "2026-06-05T15:00:00.000Z"
|
last_updated: "2026-06-05T12:42:08.692Z"
|
||||||
progress:
|
progress:
|
||||||
total_phases: 7
|
total_phases: 7
|
||||||
completed_phases: 3
|
completed_phases: 4
|
||||||
total_plans: 12
|
total_plans: 14
|
||||||
completed_plans: 12
|
completed_plans: 14
|
||||||
percent: 43
|
percent: 57
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
|
|||||||
@@ -0,0 +1,769 @@
|
|||||||
|
# Phase 6: Performance & Production Hardening — Pattern Map
|
||||||
|
|
||||||
|
**Mapped:** 2026-06-02
|
||||||
|
**Files analyzed:** 12
|
||||||
|
**Analogs found:** 9 / 12
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Classification
|
||||||
|
|
||||||
|
| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
|
||||||
|
|-------------------|------|-----------|----------------|---------------|
|
||||||
|
| `backend/deps/utils.py` | utility | request-response | `backend/deps/utils.py` (self) | self-update |
|
||||||
|
| `backend/main.py` | config/wiring | request-response | `backend/main.py` (self) | self-update |
|
||||||
|
| `backend/api/auth.py` | controller | request-response | `backend/api/auth.py` (self) | self-update |
|
||||||
|
| `backend/api/documents.py` | controller | CRUD | `backend/api/auth.py` | role-match |
|
||||||
|
| `backend/api/cloud.py` | controller | request-response | `backend/api/auth.py` | role-match |
|
||||||
|
| `backend/services/logging.py` | service | event-driven | `backend/services/auth.py` | role-match |
|
||||||
|
| `backend/config.py` | config | — | `backend/config.py` (self) | self-update |
|
||||||
|
| `backend/load_tests/locustfile.py` | test | request-response | `backend/tests/conftest.py` | partial-match |
|
||||||
|
| `backend/Dockerfile` | config | — | `backend/Dockerfile` (self) | self-update |
|
||||||
|
| `docker-compose.yml` | config | — | `docker-compose.yml` (self) | self-update |
|
||||||
|
| `docker/loki/loki-config.yaml` | config | — | none | no-analog |
|
||||||
|
| `docker/loki/promtail-config.yaml` | config | — | none | no-analog |
|
||||||
|
| `RUNBOOK.md` | documentation | — | none | no-analog |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern Assignments
|
||||||
|
|
||||||
|
### `backend/deps/utils.py` (utility, request-response) — D-11
|
||||||
|
|
||||||
|
**Change type:** Replace function body in-place. The function `get_client_ip` already exists and is imported by every router that does audit logging (`auth.py`, `documents.py`, etc.). The body must be replaced with trusted-proxy CIDR logic. Do NOT rename or add a second function.
|
||||||
|
|
||||||
|
**Current body** (`backend/deps/utils.py` lines 10–22):
|
||||||
|
```python
|
||||||
|
def get_client_ip(request: Request) -> Optional[str]:
|
||||||
|
"""Extract best-effort client IP from request for audit logging.
|
||||||
|
|
||||||
|
TRUST BOUNDARY: X-Forwarded-For is a client-controlled header and can be
|
||||||
|
forged by any caller. ...
|
||||||
|
"""
|
||||||
|
return request.headers.get("X-Forwarded-For") or (
|
||||||
|
request.client.host if request.client else None
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Replacement pattern** (from RESEARCH.md Pattern 3):
|
||||||
|
```python
|
||||||
|
import ipaddress
|
||||||
|
from typing import Optional
|
||||||
|
from fastapi import Request
|
||||||
|
|
||||||
|
_TRUSTED_PROXY_NETS = [
|
||||||
|
ipaddress.ip_network("127.0.0.0/8"),
|
||||||
|
ipaddress.ip_network("172.16.0.0/12"),
|
||||||
|
ipaddress.ip_network("192.168.0.0/16"),
|
||||||
|
ipaddress.ip_network("::1/128"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def _is_trusted_proxy(host: str) -> bool:
|
||||||
|
try:
|
||||||
|
addr = ipaddress.ip_address(host)
|
||||||
|
return any(addr in net for net in _TRUSTED_PROXY_NETS)
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_client_ip(request: Request) -> Optional[str]:
|
||||||
|
"""Extract client IP with trusted-proxy CIDR check (D-11).
|
||||||
|
|
||||||
|
If the direct peer (request.client.host) is a trusted proxy, read the
|
||||||
|
leftmost address from X-Forwarded-For. Otherwise ignore forwarded headers
|
||||||
|
and return the direct peer IP — prevents header spoofing from external clients.
|
||||||
|
"""
|
||||||
|
direct_peer = request.client.host if request.client else None
|
||||||
|
if direct_peer and _is_trusted_proxy(direct_peer):
|
||||||
|
xff = request.headers.get("X-Forwarded-For")
|
||||||
|
if xff:
|
||||||
|
return xff.split(",")[0].strip()
|
||||||
|
return direct_peer
|
||||||
|
```
|
||||||
|
|
||||||
|
**Existing import callers** (no changes required in these files — they already import the right name):
|
||||||
|
- `backend/api/auth.py` line 34: `from deps.utils import get_client_ip`
|
||||||
|
- (all other routers that call `get_client_ip(request)`)
|
||||||
|
|
||||||
|
**TRUSTED_PROXY_CIDRS config hook:** The list `_TRUSTED_PROXY_NETS` should be built from `settings.trusted_proxy_cidrs` (added in `config.py`) rather than hardcoded. The hardcoded list above is the safe default; read from config on module import after `settings` is available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/main.py` (config/wiring, request-response) — D-01, D-12
|
||||||
|
|
||||||
|
**Change type:** Add `CorrelationIDMiddleware` class, import `setup_logging`, wire `account_limiter` state.
|
||||||
|
|
||||||
|
**Existing middleware pattern** (`backend/main.py` lines 24–131) — copy exactly for the new raw-ASGI middleware class:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Existing pattern for BaseHTTPMiddleware (lines 25–42):
|
||||||
|
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
||||||
|
async def dispatch(self, request: Request, call_next):
|
||||||
|
response = await call_next(request)
|
||||||
|
response.headers["Content-Security-Policy"] = "..."
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Existing app.add_middleware() calls (lines 108–131):
|
||||||
|
app.state.limiter = auth_limiter # line 109
|
||||||
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # line 110
|
||||||
|
app.add_middleware(SlowAPIMiddleware) # line 111
|
||||||
|
app.add_middleware(SecurityHeadersMiddleware) # line 119
|
||||||
|
app.add_middleware(CORSMiddleware, ...) # line 122-128
|
||||||
|
app.add_middleware(OriginValidationMiddleware) # line 131
|
||||||
|
```
|
||||||
|
|
||||||
|
**New CorrelationIDMiddleware** — use raw ASGI (NOT BaseHTTPMiddleware) to avoid response buffering. Must be registered LAST so it runs FIRST in the request chain (Starlette reverse-insertion order):
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Add import block additions to main.py:
|
||||||
|
import uuid
|
||||||
|
import time
|
||||||
|
import structlog
|
||||||
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||||
|
|
||||||
|
logger = structlog.get_logger()
|
||||||
|
|
||||||
|
class CorrelationIDMiddleware:
|
||||||
|
"""Generate per-request correlation ID; bind to structlog contextvars.
|
||||||
|
|
||||||
|
Uses raw ASGI (not BaseHTTPMiddleware) to avoid response-body buffering.
|
||||||
|
Register LAST so it runs FIRST (Starlette reverse-insertion order).
|
||||||
|
"""
|
||||||
|
def __init__(self, app: ASGIApp) -> None:
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||||||
|
if scope["type"] != "http":
|
||||||
|
await self.app(scope, receive, send)
|
||||||
|
return
|
||||||
|
|
||||||
|
correlation_id = str(uuid.uuid4())
|
||||||
|
start_ns = time.perf_counter_ns()
|
||||||
|
|
||||||
|
structlog.contextvars.clear_contextvars()
|
||||||
|
structlog.contextvars.bind_contextvars(
|
||||||
|
correlation_id=correlation_id,
|
||||||
|
path=scope.get("path", ""),
|
||||||
|
method=scope.get("method", ""),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def send_with_header(message):
|
||||||
|
if message["type"] == "http.response.start":
|
||||||
|
headers = list(message.get("headers", []))
|
||||||
|
headers.append(
|
||||||
|
(b"x-correlation-id", correlation_id.encode())
|
||||||
|
)
|
||||||
|
message = {**message, "headers": headers}
|
||||||
|
await send(message)
|
||||||
|
|
||||||
|
await self.app(scope, receive, send_with_header)
|
||||||
|
duration_ms = (time.perf_counter_ns() - start_ns) / 1_000_000
|
||||||
|
structlog.contextvars.bind_contextvars(duration_ms=round(duration_ms, 2))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Lifespan hook — call setup_logging first** (`backend/main.py` lines 67–101 — insert before the `yield`):
|
||||||
|
```python
|
||||||
|
# In lifespan(), before the existing minio init:
|
||||||
|
from services.logging import setup_logging
|
||||||
|
setup_logging(
|
||||||
|
json_logs=settings.log_json,
|
||||||
|
log_level=settings.log_level,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**account_limiter wiring** — add alongside existing `app.state.limiter = auth_limiter`:
|
||||||
|
```python
|
||||||
|
# In main.py, alongside app.state.limiter = auth_limiter (line 109):
|
||||||
|
from services.rate_limiting import account_limiter # or wherever it lives
|
||||||
|
# account_limiter decorators work independently; no app.state wiring needed
|
||||||
|
# SlowAPIMiddleware only tracks the limiter assigned to app.state
|
||||||
|
app.state.limiter = auth_limiter # existing — drives SlowAPIMiddleware
|
||||||
|
```
|
||||||
|
|
||||||
|
**Middleware registration order** — CorrelationIDMiddleware added last (runs first), per existing Starlette convention documented at line 113–116:
|
||||||
|
```python
|
||||||
|
# After all existing app.add_middleware() calls, add last:
|
||||||
|
app.add_middleware(CorrelationIDMiddleware)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/api/auth.py` (controller, request-response) — D-11, D-13
|
||||||
|
|
||||||
|
**Change type:** Replace `key_func=get_remote_address` with `key_func=get_client_ip`. Two-line change.
|
||||||
|
|
||||||
|
**Current limiter declaration** (`backend/api/auth.py` lines 37–44):
|
||||||
|
```python
|
||||||
|
from slowapi import Limiter
|
||||||
|
from slowapi.util import get_remote_address
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||||
|
|
||||||
|
# IP-level rate limiter (SEC-02 — 10 req/min on register/login/refresh)
|
||||||
|
limiter = Limiter(key_func=get_remote_address)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Replacement**:
|
||||||
|
```python
|
||||||
|
from slowapi import Limiter
|
||||||
|
from deps.utils import get_client_ip # replace get_remote_address import
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
||||||
|
|
||||||
|
# IP-level rate limiter with trusted-proxy key function (D-11, D-13)
|
||||||
|
limiter = Limiter(key_func=get_client_ip)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Existing `@limiter.limit()` decorators remain unchanged** — they are already on the right endpoints (`lines 97–98`, `170–171`, `300–301`, `538–539`, `621–622`). Per D-13 the limits themselves (10/minute, 5/hour) are preserved.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/api/documents.py` (controller, CRUD) — D-12
|
||||||
|
|
||||||
|
**Change type:** Add `@account_limiter.limit("100/minute")` decorator and `request.state.current_user = current_user` binding to authenticated endpoints.
|
||||||
|
|
||||||
|
**Existing endpoint pattern** (`backend/api/documents.py` lines 88–101) — the handler signature and dependency injection to copy from:
|
||||||
|
```python
|
||||||
|
@router.post("/upload-url")
|
||||||
|
async def request_upload_url(
|
||||||
|
body: UploadUrlRequest,
|
||||||
|
session: AsyncSession = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_regular_user),
|
||||||
|
):
|
||||||
|
```
|
||||||
|
|
||||||
|
**New pattern with per-account rate limiting**:
|
||||||
|
```python
|
||||||
|
from backend.services.rate_limiting import account_limiter # shared module
|
||||||
|
|
||||||
|
@router.get("/")
|
||||||
|
@account_limiter.limit("100/minute")
|
||||||
|
async def list_documents(
|
||||||
|
request: Request, # Request must be first positional arg for slowapi
|
||||||
|
current_user: User = Depends(get_regular_user),
|
||||||
|
session: AsyncSession = Depends(get_db),
|
||||||
|
...
|
||||||
|
):
|
||||||
|
request.state.current_user = current_user # MUST be first line — exposes user to key_func
|
||||||
|
structlog.contextvars.bind_contextvars(user_id=str(current_user.id))
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key constraint:** `Request` must appear as the first parameter after `self` for slowapi decorators to work. Review each existing endpoint signature — `request: Request` may need to be added or moved to first position.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/api/cloud.py` (controller, request-response) — D-12
|
||||||
|
|
||||||
|
**Change type:** Same per-account rate limiting pattern as `documents.py`. The cloud router uses the same `Depends(get_regular_user)` pattern visible at `backend/api/cloud.py` lines 29–47.
|
||||||
|
|
||||||
|
**Existing endpoint signature pattern** (`backend/api/cloud.py` lines 44–47):
|
||||||
|
```python
|
||||||
|
from deps.auth import get_regular_user
|
||||||
|
from deps.db import get_db
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/cloud", tags=["cloud"])
|
||||||
|
```
|
||||||
|
|
||||||
|
**Apply the same decorator/binding pattern** as `documents.py` above to each endpoint that uses `Depends(get_regular_user)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/services/logging.py` (service, event-driven) — D-01
|
||||||
|
|
||||||
|
**Change type:** New file. No existing analog for a structlog setup module. The closest structural analog is `backend/services/auth.py` (pure Python service, no FastAPI coupling, single module with module-level init).
|
||||||
|
|
||||||
|
**Analog structure** (`backend/services/auth.py` lines 1–45):
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
Auth service — pure Python, no FastAPI coupling.
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
import logging
|
||||||
|
# ... imports ...
|
||||||
|
from config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Module-level init (PasswordHash instance)
|
||||||
|
_pwd = PasswordHash([Argon2Hasher()])
|
||||||
|
|
||||||
|
def hash_password(plain: str) -> str: ...
|
||||||
|
def verify_password(plain: str, hashed: str) -> bool: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**New file pattern** — mirror the module-level docstring, `from __future__ import annotations`, import from `config.settings`, expose a single entry-point function:
|
||||||
|
```python
|
||||||
|
"""
|
||||||
|
Structured logging setup — pure Python, no FastAPI coupling.
|
||||||
|
|
||||||
|
Call setup_logging() once in main.py lifespan before the yield.
|
||||||
|
Bridges stdlib loggers (uvicorn, sqlalchemy, celery) through the same
|
||||||
|
structlog JSON processor chain.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
from config import settings
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(json_logs: bool = False, log_level: str = "INFO") -> None:
|
||||||
|
"""Configure structlog with ProcessorFormatter bridge for stdlib loggers.
|
||||||
|
|
||||||
|
Parameters match settings.log_json and settings.log_level so callers
|
||||||
|
can pass settings values directly.
|
||||||
|
"""
|
||||||
|
timestamper = structlog.processors.TimeStamper(fmt="iso")
|
||||||
|
|
||||||
|
shared_processors = [
|
||||||
|
structlog.contextvars.merge_contextvars, # MUST be first
|
||||||
|
structlog.stdlib.add_log_level,
|
||||||
|
structlog.stdlib.add_logger_name,
|
||||||
|
structlog.stdlib.PositionalArgumentsFormatter(),
|
||||||
|
structlog.stdlib.ExtraAdder(),
|
||||||
|
timestamper,
|
||||||
|
structlog.processors.StackInfoRenderer(),
|
||||||
|
]
|
||||||
|
if json_logs:
|
||||||
|
shared_processors.append(structlog.processors.format_exc_info)
|
||||||
|
|
||||||
|
structlog.configure(
|
||||||
|
processors=shared_processors + [
|
||||||
|
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
||||||
|
],
|
||||||
|
logger_factory=structlog.stdlib.LoggerFactory(),
|
||||||
|
cache_logger_on_first_use=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
log_renderer = (
|
||||||
|
structlog.processors.JSONRenderer()
|
||||||
|
if json_logs
|
||||||
|
else structlog.dev.ConsoleRenderer()
|
||||||
|
)
|
||||||
|
|
||||||
|
formatter = structlog.stdlib.ProcessorFormatter(
|
||||||
|
foreign_pre_chain=shared_processors,
|
||||||
|
processors=[
|
||||||
|
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
||||||
|
log_renderer,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.addHandler(handler)
|
||||||
|
root_logger.setLevel(log_level.upper())
|
||||||
|
|
||||||
|
# Route uvicorn logs through structlog; suppress access log (re-emitted by middleware)
|
||||||
|
for name in ("uvicorn", "uvicorn.error"):
|
||||||
|
logging.getLogger(name).handlers.clear()
|
||||||
|
logging.getLogger(name).propagate = True
|
||||||
|
logging.getLogger("uvicorn.access").handlers.clear()
|
||||||
|
logging.getLogger("uvicorn.access").propagate = False
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/config.py` (config) — D-01, D-11
|
||||||
|
|
||||||
|
**Change type:** Add three new settings fields to the existing `Settings` class. Pattern: follow the existing field declaration style (`backend/config.py` lines 1–74) — typed field with default, grouped by phase/feature with a comment.
|
||||||
|
|
||||||
|
**Existing field pattern** (`backend/config.py` lines 48–71):
|
||||||
|
```python
|
||||||
|
# AI classification defaults (Phase 3 — D-13, D-15)
|
||||||
|
system_prompt: str = ""
|
||||||
|
default_ai_provider: str = "ollama"
|
||||||
|
default_ai_model: str = "llama3.2"
|
||||||
|
|
||||||
|
# Cloud Storage (Phase 5)
|
||||||
|
cloud_creds_key: str = "CHANGEME-32-bytes-padded!!"
|
||||||
|
google_client_id: str = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
**New fields to append** (after the Cloud Storage block, before `settings = Settings()`):
|
||||||
|
```python
|
||||||
|
# Observability (Phase 6 — D-01)
|
||||||
|
log_level: str = "INFO" # LOG_LEVEL env var; passed to setup_logging()
|
||||||
|
log_json: bool = False # LOG_JSON env var; True in production
|
||||||
|
|
||||||
|
# Rate limiting (Phase 6 — D-11)
|
||||||
|
# Comma-separated list of trusted proxy CIDRs; requests from these may set X-Forwarded-For
|
||||||
|
trusted_proxy_cidrs: list[str] = [
|
||||||
|
"127.0.0.0/8",
|
||||||
|
"172.16.0.0/12",
|
||||||
|
"192.168.0.0/16",
|
||||||
|
"::1/128",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**env_list_separator** — already set to `","` in `model_config` (line 11), so `TRUSTED_PROXY_CIDRS=127.0.0.0/8,172.16.0.0/12` is parsed correctly out of the box.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/load_tests/locustfile.py` (test, request-response) — D-04, D-05, D-06
|
||||||
|
|
||||||
|
**Change type:** New file in new directory `backend/load_tests/`. No existing Locust file. Closest analog is the auth fixture pattern in `backend/tests/conftest.py`.
|
||||||
|
|
||||||
|
**Auth pattern from conftest.py** — the login flow the Locust `on_start()` replicates (`backend/tests/conftest.py` lines 186–226):
|
||||||
|
```python
|
||||||
|
# auth_user fixture shows the login payload shape:
|
||||||
|
token = create_access_token(str(user_id), "user")
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
# Locust replicates the same via HTTP POST:
|
||||||
|
resp = self.client.post(
|
||||||
|
"/api/auth/login",
|
||||||
|
json={"email": TEST_EMAIL, "password": TEST_PASSWORD},
|
||||||
|
)
|
||||||
|
self.access_token = resp.json().get("access_token", "")
|
||||||
|
```
|
||||||
|
|
||||||
|
**New file pattern** (from RESEARCH.md Pattern 7):
|
||||||
|
```python
|
||||||
|
"""Locust load test for DocuVault — D-04, D-05, D-06.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
locust --headless --users 50 --spawn-rate 10 --run-time 5m \\
|
||||||
|
--host http://localhost:8000 \\
|
||||||
|
--csv backend/load_tests/results \\
|
||||||
|
-f backend/load_tests/locustfile.py
|
||||||
|
|
||||||
|
Prerequisites: a user with TEST_EMAIL/TEST_PASSWORD must exist in the DB.
|
||||||
|
Create via: POST /api/auth/register (on_start handles this — registers if not exists).
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from locust import HttpUser, task, between, events
|
||||||
|
|
||||||
|
TEST_EMAIL = os.environ.get("LOAD_TEST_EMAIL", "loadtest@example.com")
|
||||||
|
TEST_PASSWORD = os.environ.get("LOAD_TEST_PASSWORD", "Loadtest123!@#")
|
||||||
|
|
||||||
|
class DocuVaultUser(HttpUser):
|
||||||
|
wait_time = between(0.5, 2.0)
|
||||||
|
access_token: str = ""
|
||||||
|
|
||||||
|
def on_start(self):
|
||||||
|
# Register if not exists (catches 409 Conflict silently)
|
||||||
|
self.client.post(
|
||||||
|
"/api/auth/register",
|
||||||
|
json={"handle": "loadtestuser", "email": TEST_EMAIL, "password": TEST_PASSWORD},
|
||||||
|
)
|
||||||
|
resp = self.client.post(
|
||||||
|
"/api/auth/login",
|
||||||
|
json={"email": TEST_EMAIL, "password": TEST_PASSWORD},
|
||||||
|
)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
self.access_token = resp.json().get("access_token", "")
|
||||||
|
else:
|
||||||
|
self.environment.runner.quit()
|
||||||
|
|
||||||
|
def _auth_headers(self):
|
||||||
|
return {"Authorization": f"Bearer {self.access_token}"}
|
||||||
|
|
||||||
|
@task(5)
|
||||||
|
def list_documents(self):
|
||||||
|
self.client.get("/api/documents/", headers=self._auth_headers())
|
||||||
|
|
||||||
|
@task(2)
|
||||||
|
def upload_document(self):
|
||||||
|
# NOTE: confirm upload endpoint shape against documents.py before finalizing
|
||||||
|
# If two-step presigned flow: POST /upload-url → PUT to MinIO → POST /{id}/confirm
|
||||||
|
from io import BytesIO
|
||||||
|
data = b"%PDF-1.4 1 0 obj<</Type/Catalog>>endobj"
|
||||||
|
self.client.post(
|
||||||
|
"/api/documents/upload",
|
||||||
|
files={"file": ("test.pdf", BytesIO(data), "application/pdf")},
|
||||||
|
headers=self._auth_headers(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@task(1)
|
||||||
|
def refresh_token(self):
|
||||||
|
self.client.post("/api/auth/refresh")
|
||||||
|
|
||||||
|
|
||||||
|
@events.quitting.add_listener
|
||||||
|
def check_sla(environment, **kwargs):
|
||||||
|
stats = environment.runner.stats.total
|
||||||
|
if stats.fail_ratio > 0.01:
|
||||||
|
environment.process_exit_code = 1
|
||||||
|
elif stats.get_response_time_percentile(0.95) > 200:
|
||||||
|
environment.process_exit_code = 1
|
||||||
|
elif stats.get_response_time_percentile(0.99) > 500:
|
||||||
|
environment.process_exit_code = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Also create:** `backend/load_tests/__init__.py` (empty) so pytest does not discover this directory.
|
||||||
|
|
||||||
|
**Credentials security:** TEST_EMAIL and TEST_PASSWORD read from env vars — never hardcoded in version-controlled files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `backend/Dockerfile` (config) — D-07, D-08, D-09
|
||||||
|
|
||||||
|
**Change type:** Full replacement of single-stage build with multi-stage.
|
||||||
|
|
||||||
|
**Current file** (`backend/Dockerfile` lines 1–16):
|
||||||
|
```dockerfile
|
||||||
|
FROM python:3.12-slim
|
||||||
|
WORKDIR /app
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
tesseract-ocr libgl1 libglib2.0-0 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Replacement pattern** (from RESEARCH.md Pattern 5, D-07):
|
||||||
|
```dockerfile
|
||||||
|
# Stage 1: builder — installs Python packages as root
|
||||||
|
FROM python:3.12-slim AS builder
|
||||||
|
WORKDIR /build
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gcc \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
|
||||||
|
|
||||||
|
# Stage 2: runtime — non-root appuser, no build tools
|
||||||
|
FROM python:3.12-slim AS runtime
|
||||||
|
# Runtime system deps (tesseract-ocr, libgl1, libglib2.0-0 are required at runtime)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
tesseract-ocr \
|
||||||
|
libgl1 \
|
||||||
|
libglib2.0-0 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
COPY --from=builder /install /usr/local
|
||||||
|
RUN groupadd --gid 1000 appgroup && \
|
||||||
|
useradd --uid 1000 --gid appgroup --shell /bin/sh --no-create-home appuser
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --chown=appuser:appgroup . .
|
||||||
|
USER appuser
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note on `--prefix=/install`:** Verify that `pip install --prefix=/install` followed by `COPY --from=builder /install /usr/local` correctly populates Python site-packages in the runtime stage. An alternative is `pip install --target=/install` with `PYTHONPATH` set. The prefix approach is preferred. Verify in Wave 0 smoke test: `docker run --rm docuvault-backend:latest python -c "import structlog"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `docker-compose.yml` (config) — D-08, D-09, D-02
|
||||||
|
|
||||||
|
**Change type:** Modify existing service definitions for `backend` and `celery-worker`; add `loki`, `promtail`, `grafana` services; add named volumes.
|
||||||
|
|
||||||
|
**Existing service definition pattern** (`docker-compose.yml` lines 49–80) — the `backend` service to extend:
|
||||||
|
```yaml
|
||||||
|
backend:
|
||||||
|
build: ./backend
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- ./backend:/app
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=${DATABASE_URL}
|
||||||
|
# ... (existing env vars) ...
|
||||||
|
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Hardening additions for `backend` and `celery-worker`** (D-08, D-09):
|
||||||
|
```yaml
|
||||||
|
# Add these keys to both backend and celery-worker service definitions:
|
||||||
|
read_only: true
|
||||||
|
tmpfs:
|
||||||
|
- /tmp:mode=1777 # world-writable; appuser (uid=1000) can write; covers tempfile.NamedTemporaryFile
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
labels:
|
||||||
|
logging: "promtail" # Promtail docker_sd_configs filter label (D-02)
|
||||||
|
```
|
||||||
|
|
||||||
|
**New env vars for `backend` service** (D-01):
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
# ... existing vars ...
|
||||||
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
|
- LOG_JSON=${LOG_JSON:-false}
|
||||||
|
```
|
||||||
|
|
||||||
|
**New services block** (D-02):
|
||||||
|
```yaml
|
||||||
|
loki:
|
||||||
|
image: grafana/loki:latest
|
||||||
|
ports:
|
||||||
|
- "3100:3100"
|
||||||
|
volumes:
|
||||||
|
- ./docker/loki/loki-config.yaml:/etc/loki/local-config.yaml
|
||||||
|
- loki_data:/loki
|
||||||
|
command: -config.file=/etc/loki/local-config.yaml
|
||||||
|
|
||||||
|
promtail:
|
||||||
|
image: grafana/promtail:latest
|
||||||
|
volumes:
|
||||||
|
- ./docker/loki/promtail-config.yaml:/etc/promtail/config.yaml
|
||||||
|
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
command: -config.file=/etc/promtail/config.yaml
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:latest
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- GF_AUTH_ANONYMOUS_ENABLED=true
|
||||||
|
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
|
||||||
|
volumes:
|
||||||
|
- grafana_data:/var/lib/grafana
|
||||||
|
depends_on:
|
||||||
|
- loki
|
||||||
|
```
|
||||||
|
|
||||||
|
**New volumes** (append to existing `volumes:` block):
|
||||||
|
```yaml
|
||||||
|
volumes:
|
||||||
|
postgres_data: # existing
|
||||||
|
minio_data: # existing
|
||||||
|
loki_data: # new
|
||||||
|
grafana_data: # new
|
||||||
|
```
|
||||||
|
|
||||||
|
**Celery-beat exclusion:** D-08 says `read_only: true` applies to "FastAPI and Celery worker services" — the `celery-beat` service writes `celerybeat-schedule` to its working directory. Do NOT apply `read_only: true` to `celery-beat`. If hardening is desired later, add `--schedule /tmp/celerybeat-schedule` to its command.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Shared Patterns
|
||||||
|
|
||||||
|
### Existing Limiter declaration (all auth endpoint rate limiting)
|
||||||
|
|
||||||
|
**Source:** `backend/api/auth.py` lines 37–44
|
||||||
|
**Apply to:** `backend/api/auth.py` (replace `get_remote_address` with `get_client_ip`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Current (to be replaced):
|
||||||
|
from slowapi.util import get_remote_address
|
||||||
|
limiter = Limiter(key_func=get_remote_address)
|
||||||
|
|
||||||
|
# Replacement:
|
||||||
|
from deps.utils import get_client_ip
|
||||||
|
limiter = Limiter(key_func=get_client_ip)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-account rate limiter (second Limiter instance)
|
||||||
|
|
||||||
|
**Source:** RESEARCH.md Pattern 4
|
||||||
|
**Apply to:** `backend/api/documents.py`, `backend/api/cloud.py`
|
||||||
|
**Where to define it:** A shared module — `backend/api/rate_limiting.py` or `backend/main.py` — so both document and cloud routers import the same instance.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from slowapi import Limiter
|
||||||
|
from fastapi import Request
|
||||||
|
|
||||||
|
def _account_key(request: Request) -> str:
|
||||||
|
user = getattr(request.state, "current_user", None)
|
||||||
|
if user is None:
|
||||||
|
return request.client.host if request.client else "anonymous"
|
||||||
|
return str(user.id)
|
||||||
|
|
||||||
|
account_limiter = Limiter(key_func=_account_key)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Usage in each authenticated endpoint:
|
||||||
|
@router.get("/")
|
||||||
|
@account_limiter.limit("100/minute")
|
||||||
|
async def list_documents(
|
||||||
|
request: Request, # must be first positional param
|
||||||
|
current_user: User = Depends(get_regular_user),
|
||||||
|
...
|
||||||
|
):
|
||||||
|
request.state.current_user = current_user # expose to key_func — MUST be first line
|
||||||
|
structlog.contextvars.bind_contextvars(user_id=str(current_user.id))
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Structlog logger usage in route handlers
|
||||||
|
|
||||||
|
**Source:** RESEARCH.md Code Examples section
|
||||||
|
**Apply to:** `backend/api/documents.py`, `backend/api/cloud.py`, any router that has authenticated endpoints
|
||||||
|
|
||||||
|
```python
|
||||||
|
import structlog
|
||||||
|
log = structlog.get_logger()
|
||||||
|
|
||||||
|
async def some_endpoint(..., current_user: User = Depends(get_regular_user)):
|
||||||
|
structlog.contextvars.bind_contextvars(user_id=str(current_user.id))
|
||||||
|
log.info("event.name", field=value)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pydantic Settings field pattern
|
||||||
|
|
||||||
|
**Source:** `backend/config.py` lines 13–72
|
||||||
|
**Apply to:** All new env vars in `backend/config.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Pattern: typed field + default value + inline comment with phase reference
|
||||||
|
field_name: type = default_value # ENV_VAR_NAME env var; description (Phase N — Decision ref)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async test client with auth headers
|
||||||
|
|
||||||
|
**Source:** `backend/tests/conftest.py` lines 186–226
|
||||||
|
**Apply to:** `backend/tests/test_logging.py`, `backend/tests/test_rate_limiting.py`
|
||||||
|
|
||||||
|
```python
|
||||||
|
@pytest_asyncio.fixture
|
||||||
|
async def auth_user(db_session: AsyncSession):
|
||||||
|
# Returns: {"user": User, "token": str, "headers": {"Authorization": "Bearer <token>"}}
|
||||||
|
...
|
||||||
|
|
||||||
|
# Usage in test:
|
||||||
|
async def test_something(async_client, auth_user):
|
||||||
|
resp = await async_client.get("/api/documents/", headers=auth_user["headers"])
|
||||||
|
assert resp.status_code == 200
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No Analog Found
|
||||||
|
|
||||||
|
Files with no close match in the codebase (planner should use RESEARCH.md patterns directly):
|
||||||
|
|
||||||
|
| File | Role | Data Flow | Reason |
|
||||||
|
|------|------|-----------|--------|
|
||||||
|
| `docker/loki/loki-config.yaml` | config | — | No YAML service configs exist in the repo; use RESEARCH.md Pattern 6 (loki-config.yaml section) verbatim |
|
||||||
|
| `docker/loki/promtail-config.yaml` | config | — | No YAML service configs exist in the repo; use RESEARCH.md Pattern 6 (promtail-config.yaml section) verbatim |
|
||||||
|
| `RUNBOOK.md` | documentation | — | No operational runbook exists; D-14 describes content fully |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Notes for Planner
|
||||||
|
|
||||||
|
1. **`get_client_ip` is a body replacement, not a new function.** CLAUDE.md mandates one canonical definition in `deps/utils.py`. Every router already imports it by name — the import chain stays intact.
|
||||||
|
|
||||||
|
2. **`CorrelationIDMiddleware` must use raw ASGI, not `BaseHTTPMiddleware`.** The existing `SecurityHeadersMiddleware` and `OriginValidationMiddleware` use `BaseHTTPMiddleware` — do not copy that pattern for `CorrelationIDMiddleware`. See RESEARCH.md Anti-Patterns section.
|
||||||
|
|
||||||
|
3. **Locust must NOT be in `requirements.txt`.** It is a dev/external tool. Add to `requirements-dev.txt` or run from a host virtualenv. The locustfile has zero imports from the application codebase.
|
||||||
|
|
||||||
|
4. **`read_only: true` excludes `celery-beat`** (Pitfall 7 in RESEARCH.md). The D-08 scope is "FastAPI and Celery worker services" only.
|
||||||
|
|
||||||
|
5. **`tmpfs: - /tmp:mode=1777`** is required (not just `/tmp`). Without `mode=1777`, appuser (uid=1000) cannot write to the tmpfs-mounted `/tmp` — `services/extractor.py` uses `tempfile.NamedTemporaryFile()` which writes to `/tmp`.
|
||||||
|
|
||||||
|
6. **Wave 0 assumption to verify:** `request.state.current_user` set as first line of handler body must be read correctly by slowapi's `key_func` before it increments the counter. Write a unit test (`test_rate_limiting.py::test_account_limiter_key`) before applying the decorator to all endpoints.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Analog search scope:** `backend/api/`, `backend/services/`, `backend/deps/`, `backend/tests/`, `docker-compose.yml`, `backend/Dockerfile`, `backend/config.py`
|
||||||
|
**Files scanned:** 13
|
||||||
|
**Pattern extraction date:** 2026-06-02
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
phase: 06-performance-production-hardening
|
||||||
|
fixed_at: 2026-06-04T00:00:00Z
|
||||||
|
review_path: .planning/phases/06-performance-production-hardening/06-REVIEW.md
|
||||||
|
iteration: 1
|
||||||
|
findings_in_scope: 16
|
||||||
|
fixed: 16
|
||||||
|
skipped: 0
|
||||||
|
status: all_fixed
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 6: Code Review Fix Report
|
||||||
|
|
||||||
|
**Fixed at:** 2026-06-04
|
||||||
|
**Source review:** `.planning/phases/06-performance-production-hardening/06-REVIEW.md`
|
||||||
|
**Iteration:** 1
|
||||||
|
|
||||||
|
**Summary:**
|
||||||
|
- Findings in scope: 16 (7 Critical + 9 Warning)
|
||||||
|
- Fixed: 16
|
||||||
|
- Skipped: 0
|
||||||
|
|
||||||
|
## Fixed Issues
|
||||||
|
|
||||||
|
### CR-01: CLOUD_CREDS_KEY never passed to backend service
|
||||||
|
|
||||||
|
**Files modified:** `docker-compose.yml`
|
||||||
|
**Commit:** a8dbb02
|
||||||
|
**Applied fix:** Added `- CLOUD_CREDS_KEY=${CLOUD_CREDS_KEY}` to the backend service environment block, alongside WR-02, WR-05, WR-06, WR-08.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-02: Grafana exposed with unauthenticated Admin-role access
|
||||||
|
|
||||||
|
**Files modified:** `docker-compose.yml`
|
||||||
|
**Commit:** a8dbb02
|
||||||
|
**Applied fix:** Changed Grafana env to disable anonymous access and use `${GRAFANA_ADMIN_USER:-admin}` / `${GRAFANA_ADMIN_PASSWORD:-changeme}` credentials. Changed port bindings for both Grafana (`3000:3000`) and Loki (`3100:3100`) to loopback-only (`127.0.0.1:3000:3000` and `127.0.0.1:3100:3100`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-03: `get_client_ip()` bypassed — raw `X-Forwarded-For` reads
|
||||||
|
|
||||||
|
**Files modified:** `backend/api/cloud.py`, `backend/api/documents.py`
|
||||||
|
**Commit:** 23c27ef
|
||||||
|
**Applied fix:** Added `from deps.utils import get_client_ip` import to both files. Replaced all five raw `request.headers.get("X-Forwarded-For")` reads (cloud.py lines 629, 766; documents.py lines 276, 384, 670) with `get_client_ip(request)`. Also removed the "TRUST BOUNDARY" comments that noted the problem without fixing it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-04: `default_storage_backend` written to DB without allowlist validation
|
||||||
|
|
||||||
|
**Files modified:** `backend/api/cloud.py`
|
||||||
|
**Commit:** 3a6251c
|
||||||
|
**Applied fix:** Added `_VALID_BACKENDS = frozenset({"minio", "google_drive", "onedrive", "nextcloud", "webdav"})` module constant and validation block in `update_default_storage()` that raises HTTP 422 for any value not in the allowlist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-05: Audit log leaks attempted email (PII) in `metadata_`
|
||||||
|
|
||||||
|
**Files modified:** `backend/api/auth.py`, `frontend/src/components/admin/AuditLogTab.vue`
|
||||||
|
**Commit:** aad7635
|
||||||
|
**Applied fix:** Added `import hashlib` to auth.py and replaced `{"attempted_email": str(body.email)}` with `{"attempted_email_hash": hashlib.sha256(str(body.email).encode()).hexdigest()[:16]}` in the login failure audit log call. Updated AuditLogTab.vue to display `entry.metadata_.attempted_email_hash` (with `hash:` prefix and monospace styling) instead of `entry.metadata_.attempted_email`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-06: `CorrelationIDMiddleware` binds `duration_ms` after response is delivered — value never logged
|
||||||
|
|
||||||
|
**Files modified:** `backend/main.py`
|
||||||
|
**Commit:** a37a910
|
||||||
|
**Applied fix:** Added `_response_status: int = 0` variable and `nonlocal _response_status` capture in the `send_with_header` closure to record the HTTP status code. After `await self.app(...)` computes `duration_ms`, now emits a structured log line via `structlog.get_logger("docuvault.access").info("request_complete", status_code=_response_status)` so `duration_ms` is written to the log before context is cleared.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CR-07: `event_type` LIKE filter allows unvalidated user input with SQL wildcards
|
||||||
|
|
||||||
|
**Files modified:** `backend/api/audit.py`, `backend/tests/test_audit.py`
|
||||||
|
**Commit:** 10970d9 (fix), fb4ce29 (test update)
|
||||||
|
**Applied fix:** Added `_VALID_EVENT_PREFIXES = frozenset({"auth", "document", "folder", "share", "admin", "cloud"})` module constant. Added validation before each of the three `.like()` call sites (`_build_filtered_query`, `_build_filtered_query_with_handles`, and the inline count query in `list_audit_log`). Changed LIKE pattern from `f"{event_type}%"` to `f"{event_type}.%"` to enforce true prefix semantics. Updated `test_audit_log_filter_by_event_type` to pass `"document"` prefix instead of the full `"document.uploaded"` event type string.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-01: `auth_limiter` not reset between tests
|
||||||
|
|
||||||
|
**Files modified:** `backend/tests/conftest.py`
|
||||||
|
**Commit:** 4a57193
|
||||||
|
**Applied fix:** Added `from api.auth import limiter as auth_limiter` import and added `auth_limiter._storage.reset()` calls both before and after `yield` in the `reset_rate_limiter` autouse fixture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-02: `uvicorn --reload` in docker-compose production backend command
|
||||||
|
|
||||||
|
**Files modified:** `docker-compose.yml`
|
||||||
|
**Commit:** a8dbb02
|
||||||
|
**Applied fix:** Changed `command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload` to `command: uvicorn main:app --host 0.0.0.0 --port 8000 --workers 2`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-03: Locust load test accesses document list as a bare list — shape mismatch
|
||||||
|
|
||||||
|
**Files modified:** `backend/load_tests/locustfile.py`
|
||||||
|
**Commit:** 013802a
|
||||||
|
**Applied fix:** Changed `docs = resp.json()` to `docs = resp.json().get("items", [])` in the `get_document` task so it correctly handles the `{"items": [...], "total": N, ...}` response envelope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-04: `trusted_proxy` list missing `10.0.0.0/8`
|
||||||
|
|
||||||
|
**Files modified:** `backend/deps/utils.py`
|
||||||
|
**Commit:** b0d2406
|
||||||
|
**Applied fix:** Added `ipaddress.ip_network("10.0.0.0/8")` as the first entry in `_TRUSTED_PROXY_NETS`, covering cloud VPC, Kubernetes pod CIDRs, and custom Docker network configurations that use the 10.x.x.x range.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-05: `celery-beat` service lacks container hardening
|
||||||
|
|
||||||
|
**Files modified:** `docker-compose.yml`
|
||||||
|
**Commit:** a8dbb02
|
||||||
|
**Applied fix:** Added `read_only: true`, `tmpfs: ["/tmp:mode=1777"]`, `cap_drop: [ALL]`, and `security_opt: ["no-new-privileges:true"]` to the celery-beat service. Changed the `command` to pass `--schedule /tmp/celerybeat-schedule` so the schedule file goes to the tmpfs mount instead of the read-only root filesystem. Removed the "NOT hardened" comment.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-06: `LOG_JSON` hardcoded to `true` in docker-compose
|
||||||
|
|
||||||
|
**Files modified:** `docker-compose.yml`
|
||||||
|
**Commit:** a8dbb02
|
||||||
|
**Applied fix:** Changed `- LOG_JSON=${LOG_JSON:-false}` (was `- LOG_JSON=true #${LOG_JSON:-false}` in working tree) to `- LOG_JSON=${LOG_JSON:-true}` — defaults to `true` in production but allows developer override via `.env`. The default changed from `false` to `true` to match production logging intent.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-07: `print()` used for cloud delete errors instead of structlog
|
||||||
|
|
||||||
|
**Files modified:** `backend/api/documents.py`
|
||||||
|
**Commit:** 7cd29e9
|
||||||
|
**Applied fix:** Added `import structlog as _structlog` and `_log = _structlog.get_logger(__name__)` at module level. Replaced `import sys; print(f"[cloud-delete] provider error: {exc}", file=sys.stderr)` with `_log.warning("cloud_delete_failed", provider=doc.storage_backend, error=str(exc))`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-08: `celery-worker` missing `SECRET_KEY`
|
||||||
|
|
||||||
|
**Files modified:** `docker-compose.yml`
|
||||||
|
**Commit:** a8dbb02
|
||||||
|
**Applied fix:** Added `- SECRET_KEY=${SECRET_KEY}` to the celery-worker service environment block.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### WR-09: `AuditLogTab.vue` silently swallows fetch errors — no user feedback
|
||||||
|
|
||||||
|
**Files modified:** `frontend/src/components/admin/AuditLogTab.vue`
|
||||||
|
**Commit:** 21366bd
|
||||||
|
**Applied fix:** Added `const fetchError = ref(null)` reactive ref. Set `fetchError.value = null` at the start of `fetchLog()` and `fetchError.value = 'Failed to load audit log. Please try again.'` in the catch block. Added `<p v-else-if="fetchError" class="text-xs text-red-600 mt-1">{{ fetchError }}</p>` between the loading state and empty state elements in the template.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Results
|
||||||
|
|
||||||
|
Backend test suite run after all fixes:
|
||||||
|
- **366 passed**, 1 failed (pre-existing `test_extract_docx` — `ModuleNotFoundError: No module named 'docx'` in local dev environment, unrelated to these fixes), 6 skipped, 12 xfailed.
|
||||||
|
- The `test_audit_log_filter_by_event_type` failure from the CR-07 fix was resolved by updating the test to use the prefix-based filter API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Fixed: 2026-06-04_
|
||||||
|
_Fixer: Claude (gsd-code-fixer)_
|
||||||
|
_Iteration: 1_
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
# Phase 7.2: Security — JTI Claim + Redis Access-Token Revocation - Context
|
||||||
|
|
||||||
|
**Gathered:** 2026-06-05
|
||||||
|
**Status:** Ready for planning
|
||||||
|
|
||||||
|
<domain>
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
Phase 7.2 delivers two security primitives: (1) a `jti` UUID claim embedded in every new access token, and (2) a `user_nbf:{user_id}` Redis key checked in `get_current_user` that immediately invalidates all tokens issued before a security event (password change, TOTP enroll/revoke, account deactivation). This closes the 15-minute gap where a revoked session's live access token remains valid even after its refresh tokens are killed.
|
||||||
|
|
||||||
|
No schema migrations. No new endpoints. No frontend changes.
|
||||||
|
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
<decisions>
|
||||||
|
## Implementation Decisions
|
||||||
|
|
||||||
|
### D-01 — JTI claim
|
||||||
|
Add `jti=str(uuid.uuid4())` to the `create_access_token` payload in `backend/services/auth.py`. The `jti` claim is included on every token but is used only as an identity handle — per-JTI Redis tracking is NOT implemented (user-level NBF replaces it, see D-02).
|
||||||
|
|
||||||
|
### D-02 — Revocation approach: user-level NBF key, not per-JTI
|
||||||
|
Set `user_nbf:{user_id}` = Unix timestamp in Redis (TTL = 15 min, the access token lifetime) when any of these security events fire: `change_password`, `enable_totp`, `disable_totp`, admin account deactivation.
|
||||||
|
|
||||||
|
In `get_current_user`, after a successful decode, read `user_nbf:{user_id}` from Redis. If the key exists and `token["iat"] < nbf_timestamp` → raise HTTP 401. This covers ALL sessions uniformly — the calling session's own token is also immediately blocked (its iat predates now()), but it recovers transparently by exchanging its still-live refresh token (which Phase 7.1 D-02 preserved) for a new access token with iat > nbf.
|
||||||
|
|
||||||
|
Per-JTI tracking is not added: the jti claim is reserved for Phase 7.3+ (ES256 migration) where it may be needed alongside the algorithm upgrade.
|
||||||
|
|
||||||
|
### D-03 — Redis access pattern in get_current_user
|
||||||
|
Use `request.app.state.redis` directly inside `get_current_user`. This is consistent with the existing pattern in `backend/api/auth.py` (lines 197, 566). `get_current_user` already accepts `request: Request`, so no signature change is needed. No new `get_redis` dependency function.
|
||||||
|
|
||||||
|
### D-04 — Redis failure handling: fail-open
|
||||||
|
If the Redis `GET user_nbf:{user_id}` call raises an exception (connection error, timeout), log a warning and allow the request to proceed. Same pattern as the HIBP fail-open in `services/auth.py`. Availability takes priority over blocking tokens during a Redis outage.
|
||||||
|
|
||||||
|
### D-05 — Admin account deactivation coverage
|
||||||
|
The existing deactivation handler in `backend/api/admin.py:351` already calls `revoke_all_refresh_tokens`. Add the `user_nbf` write there too. The target user's access token (unknown JTI) is fully blocked via the user-level key; no per-JTI lookup needed.
|
||||||
|
|
||||||
|
### D-06 — TTL for user_nbf key
|
||||||
|
15 minutes — matches the access token TTL. Keys auto-expire when the last old token would have anyway. No Redis accumulation over time.
|
||||||
|
|
||||||
|
</decisions>
|
||||||
|
|
||||||
|
<canonical_refs>
|
||||||
|
## Canonical References
|
||||||
|
|
||||||
|
**Downstream agents MUST read these before planning or implementing.**
|
||||||
|
|
||||||
|
### Authentication service
|
||||||
|
- `backend/services/auth.py` — `create_access_token` (~line 86): add `jti` claim here; `revoke_all_refresh_tokens` (~line 218): pattern for where to add `user_nbf` writes; TOTP `verify_totp` (~line 271): example of Redis access from a service function
|
||||||
|
- `backend/deps/auth.py` — `get_current_user` (~line 38): the NBF check is added here, after `decode_access_token` succeeds and before the DB lookup
|
||||||
|
|
||||||
|
### Admin deactivation
|
||||||
|
- `backend/api/admin.py:351` — deactivation handler: add `user_nbf` write here alongside `revoke_all_refresh_tokens`
|
||||||
|
|
||||||
|
### Phase 7.1 decisions (must not conflict)
|
||||||
|
- `.planning/phases/07.1-security-session-revocation-on-privilege-change/07.1-CONTEXT.md` — D-02: current refresh token is preserved on privilege change; this is why the 401→refresh recovery in D-02 above works
|
||||||
|
|
||||||
|
### Security concern definitions
|
||||||
|
- `.planning/codebase/CONCERNS.md` §"No JTI Claim and No JTI Revocation in Redis" — original concern description and fix approach
|
||||||
|
|
||||||
|
### CLAUDE.md security rules
|
||||||
|
- `CLAUDE.md` §"Login token hardening" — mandates JTI in every token stored in Redis; and "Password change, TOTP enroll/revoke, and account deactivation immediately revoke all active sessions"
|
||||||
|
|
||||||
|
</canonical_refs>
|
||||||
|
|
||||||
|
<code_context>
|
||||||
|
## Existing Code Insights
|
||||||
|
|
||||||
|
### Reusable Assets
|
||||||
|
- `create_access_token` in `services/auth.py:86` — add `jti=str(uuid.uuid4())` to the payload dict; PyJWT encodes it verbatim
|
||||||
|
- `decode_access_token` in `services/auth.py:102` — returns the full payload dict; jti is accessible as `payload["jti"]` after decode
|
||||||
|
- `app.state.redis` — aioredis client, `await redis.get(key)` returns `bytes | None`; `await redis.set(key, value, ex=seconds)` writes with TTL
|
||||||
|
|
||||||
|
### Established Patterns
|
||||||
|
- Redis key style: `totp_used:{user_id}:{code}` — use same `snake_case:colon` pattern → `user_nbf:{user_id}`
|
||||||
|
- Fail-open: `backend/services/auth.py` HIBP check returns `False` on network errors and logs a warning — mirror this exact pattern for the Redis check in `get_current_user`
|
||||||
|
- `request.app.state.redis` access: `backend/api/auth.py:197` and `:566` — copy this inline access pattern verbatim
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- `backend/services/auth.py` — `create_access_token`: only file generating JWTs; change here propagates to all token issuance
|
||||||
|
- `backend/deps/auth.py` — `get_current_user`: only entry point for validating access tokens on all protected routes; NBF check belongs here
|
||||||
|
- `backend/api/auth.py` — `change_password`, `enable_totp`, `disable_totp`: add `await redis.set(f"user_nbf:{user_id}", int(time.time()), ex=900)` before `session.commit()`
|
||||||
|
- `backend/api/admin.py:351` — deactivation handler: same Redis write pattern
|
||||||
|
|
||||||
|
</code_context>
|
||||||
|
|
||||||
|
<specifics>
|
||||||
|
## Specific Ideas
|
||||||
|
|
||||||
|
- The `user_nbf` value stored in Redis should be a Unix integer timestamp (`int(time.time())`). In `get_current_user`, parse it as `int(nbf_bytes.decode())` and compare to `payload["iat"]` (which PyJWT decodes as an int automatically).
|
||||||
|
- TTL = 900 seconds (15 * 60) — hardcode this constant alongside `ACCESS_TOKEN_EXPIRE_MINUTES` in settings or services/auth.py.
|
||||||
|
- The `jti` payload key is lowercase, consistent with RFC 7519. PyJWT accepts arbitrary keys in the payload dict.
|
||||||
|
|
||||||
|
</specifics>
|
||||||
|
|
||||||
|
<deferred>
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- **Per-JTI Redis tracking** — storing each issued JTI in Redis for individual token revocation. Not needed because user-level NBF covers the same use case more efficiently.
|
||||||
|
- **Phase 7.3 — ES256 algorithm upgrade**: Replace HS256 with ECDSA P-256 key pair. The jti claim added in this phase is a prerequisite. Tracked in CONCERNS.md §"JWT Algorithm Downgrade: HS256 Instead of ES256".
|
||||||
|
- **Phase 7.4 — Token fingerprinting / token binding**: Add `fgp` (fingerprint) claim = HMAC of `User-Agent + Accept-Language`. Tracked in CONCERNS.md §"No Token Fingerprint / Token Binding".
|
||||||
|
|
||||||
|
</deferred>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 07.2-security-jti-claim-redis-access-token-revocation-inserted*
|
||||||
|
*Context gathered: 2026-06-05*
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
# Phase 7.2: Security — JTI Claim + Redis Access-Token Revocation - Discussion Log
|
||||||
|
|
||||||
|
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||||
|
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||||
|
|
||||||
|
**Date:** 2026-06-05
|
||||||
|
**Phase:** 07.2-security-jti-claim-redis-access-token-revocation-inserted
|
||||||
|
**Areas discussed:** Revocation scope, Redis injection, Redis failure behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Revocation scope
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Current session only (per-JTI) | Revoke only the JTI in the current request. Other sessions' access tokens stay valid up to 15 min — refresh tokens already gone (Phase 7.1), so no extension possible. | |
|
||||||
|
| All sessions (user-level NBF key) | Set `user_nbf:{user_id}` = now() in Redis. `get_current_user` checks `token.iat < nbf` — all tokens issued before the event are immediately invalid. One extra Redis read per request. | ✓ |
|
||||||
|
|
||||||
|
**Follow-up — TTL for user_nbf key:**
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| 15 minutes (access token TTL) | Keys auto-expire when the last old token would anyway. Minimal Redis footprint. | ✓ |
|
||||||
|
| Indefinitely (no TTL) | Extra safety margin; accumulates keys permanently. | |
|
||||||
|
|
||||||
|
**Follow-up — calling session's own token is also blocked:**
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Transparent refresh is fine | 401 → refresh (still-live cookie) → new token. Current session recovers silently. Clean uniform implementation. | ✓ |
|
||||||
|
| Keep current session alive | Requires JTI whitelist, adds complexity. | |
|
||||||
|
|
||||||
|
**Notes:** User-level NBF cleanly covers all sessions without per-JTI tracking infrastructure. The 401→refresh recovery is transparent because Phase 7.1 D-02 kept the current refresh token alive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Redis injection
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| `request.app.state.redis` (inline) | `get_current_user` already has `request: Request`. Consistent with auth.py handler pattern (lines 197, 566). Zero new infrastructure. | ✓ |
|
||||||
|
| `get_redis` FastAPI dependency | More testable in isolation but adds a new dependency function and changes `get_current_user` signature. | |
|
||||||
|
|
||||||
|
**Notes:** Consistency with existing inline pattern was the deciding factor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Redis failure behavior
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Fail-open: allow the request through | Log a warning, skip the check. Same as HIBP pattern. Availability wins. | ✓ |
|
||||||
|
| Fail-closed: raise 503 | Security wins; any Redis blip takes down authentication. | |
|
||||||
|
|
||||||
|
**Notes:** Matches the HIBP fail-open philosophy already established in the codebase.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Per-JTI Redis tracking (superseded by user-level NBF)
|
||||||
|
- Phase 7.3 — ES256 algorithm upgrade
|
||||||
|
- Phase 7.4 — Token fingerprinting / token binding
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Phase 7.3: Security — ES256 Algorithm Upgrade - Context
|
||||||
|
|
||||||
|
**Gathered:** 2026-06-05
|
||||||
|
**Status:** Ready for planning
|
||||||
|
|
||||||
|
<domain>
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
Phase 7.3 delivers three auth-security upgrades in one cohesive change:
|
||||||
|
|
||||||
|
1. **ES256 algorithm upgrade** — Replace HS256 with ECDSA P-256 (ES256) across all four JWT signing/verification sites in `services/auth.py`. Generate a P-256 key pair; store base64-encoded PEM keys as `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` env vars. A leaked public key cannot forge tokens.
|
||||||
|
2. **Startup token rotation** — On first boot after the ES256 deploy, bulk-revoke all existing `RefreshToken` rows (set `revoked=True`). Detect the migration via the `system_settings` table; update the stored `jwt_algorithm` value after rotation so subsequent restarts do not re-revoke.
|
||||||
|
3. **"Remember me" session duration** — Default refresh token TTL changes from 30 days to 16 hours. A `remember_me: bool = False` param on the login endpoint (and a checkbox in `LoginView.vue`) opts the user into the existing 30-day TTL. Access token TTL stays at 15 minutes (CLAUDE.md non-negotiable).
|
||||||
|
|
||||||
|
No other changes are in scope for this phase.
|
||||||
|
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
<decisions>
|
||||||
|
## Implementation Decisions
|
||||||
|
|
||||||
|
### Key Format and Storage
|
||||||
|
- **D-01:** Both `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` are **required** env vars. Both store the PEM key **base64-encoded** as a single line — shell-safe, no newline-escape complications. `config.py` decodes each value before passing to PyJWT.
|
||||||
|
- **D-02:** `create_access_token` and `create_password_reset_token` sign with the private key using `algorithm="ES256"`. `decode_access_token` and `decode_password_reset_token` verify with the public key using `algorithms=["ES256"]`.
|
||||||
|
- **D-03:** `settings.secret_key` is **removed from all JWT code** after ES256 is in place. It is no longer used for JWT signing. The `SECRET_KEY` env var stays in docker-compose for any future non-JWT HMAC needs (Phase 7.4 fingerprinting), but `services/auth.py` must not reference it.
|
||||||
|
|
||||||
|
### Startup Token Rotation
|
||||||
|
- **D-04:** On FastAPI `lifespan` startup, compare the `jwt_algorithm` key in the `system_settings` table (or its absence) against the current algorithm `"ES256"`. If the stored value differs (e.g., was `"HS256"` or is missing), bulk-revoke all `RefreshToken` rows by executing `UPDATE refresh_tokens SET revoked = true WHERE revoked = false`, then upsert `jwt_algorithm = "ES256"` into `system_settings`. This is idempotent — safe to restart.
|
||||||
|
- **D-05:** Use the existing `system_settings` upsert pattern from `backend/services/ai_config.py` (`seed_system_settings_from_env`) as the reference for reading/writing system settings in the startup hook.
|
||||||
|
|
||||||
|
### Key Generation Workflow
|
||||||
|
- **D-06:** Document key generation as a **Python one-liner** in `README.md` using the `cryptography` library (already a project dependency). The snippet prints both base64-encoded keys ready to paste into `.env`. No extra tooling required.
|
||||||
|
- **D-07:** `docker-compose.yml` references `${JWT_PRIVATE_KEY}` and `${JWT_PUBLIC_KEY}` from the `.env` file — same pattern as the existing `${SECRET_KEY}`. No placeholder values in the repo.
|
||||||
|
|
||||||
|
### Session Duration / "Remember Me"
|
||||||
|
- **D-08:** Access token TTL: **15 minutes** — unchanged and non-negotiable (CLAUDE.md).
|
||||||
|
- **D-09:** Default refresh token TTL: **16 hours** (new config setting `refresh_token_expire_hours: int = 16`). This guarantees session expiry overnight and covers a full workday.
|
||||||
|
- **D-10:** "Remember me" opt-in: **30 days** (existing `refresh_token_expire_days: int = 30` kept as-is). When `remember_me=True` is passed to the login endpoint, `create_refresh_token` uses the 30-day TTL; otherwise it uses 16 hours.
|
||||||
|
- **D-11:** `POST /api/auth/login` request body gains `remember_me: bool = False`. `create_refresh_token` gains `remember_me: bool = False` param — selects between `timedelta(hours=settings.refresh_token_expire_hours)` and `timedelta(days=settings.refresh_token_expire_days)`.
|
||||||
|
- **D-12:** `LoginView.vue` gains a "Remember me for 30 days" checkbox that passes `remember_me` to `authStore.login()`. The store passes it through to the API call.
|
||||||
|
|
||||||
|
</decisions>
|
||||||
|
|
||||||
|
<canonical_refs>
|
||||||
|
## Canonical References
|
||||||
|
|
||||||
|
**Downstream agents MUST read these before planning or implementing.**
|
||||||
|
|
||||||
|
### JWT signing — target code (4 sites to change)
|
||||||
|
- `backend/services/auth.py` lines 84–150 — `create_access_token` (line 86), `decode_access_token` (line 102), `create_password_reset_token` (line 121), `decode_password_reset_token` (line 135); all currently use `settings.secret_key` / `"HS256"`
|
||||||
|
|
||||||
|
### Config — where new settings land
|
||||||
|
- `backend/config.py` line 31 — current `secret_key: str = "CHANGEME"`; add `jwt_private_key: str`, `jwt_public_key: str`, `refresh_token_expire_hours: int = 16` here
|
||||||
|
|
||||||
|
### Startup hook — where rotation logic goes
|
||||||
|
- `backend/main.py` line 136 — `lifespan` function; the ES256 rotation check goes inside this existing startup block, after the Redis check and before the AI seed
|
||||||
|
- `backend/services/ai_config.py` — reference implementation for reading/writing `system_settings` key-value pairs at startup (`seed_system_settings_from_env` pattern)
|
||||||
|
|
||||||
|
### Models — token and settings tables
|
||||||
|
- `backend/db/models.py` line 87 — `RefreshToken` model with `revoked: Mapped[bool]` column (line 102) — target for bulk-revoke UPDATE
|
||||||
|
- `backend/db/models.py` line 340 — `SystemSettings` model — storage for `jwt_algorithm` detection key
|
||||||
|
|
||||||
|
### Login endpoint — remember_me param
|
||||||
|
- `backend/api/auth.py` — `POST /api/auth/login` handler; add `remember_me: bool = False` to the request schema and pass it through to `create_refresh_token`
|
||||||
|
|
||||||
|
### Frontend login form
|
||||||
|
- `frontend/src/views/auth/LoginView.vue` line 228 — `authStore.login(email, password)` call; extend to pass `remember_me` bool from new checkbox
|
||||||
|
|
||||||
|
### Security requirement
|
||||||
|
- `CLAUDE.md` §"Login token hardening" — access token TTL 15 min max, ES256 asymmetric algorithm, refresh token rotation; this phase enforces all three
|
||||||
|
- `.planning/codebase/CONCERNS.md` §"JWT Algorithm Downgrade: HS256 Instead of ES256" — full risk description and fix approach
|
||||||
|
|
||||||
|
### docker-compose env var pattern
|
||||||
|
- `docker-compose.yml` lines ~60–65 — existing `SECRET_KEY=${SECRET_KEY}` pattern; `JWT_PRIVATE_KEY` and `JWT_PUBLIC_KEY` follow the same form
|
||||||
|
|
||||||
|
</canonical_refs>
|
||||||
|
|
||||||
|
<code_context>
|
||||||
|
## Existing Code Insights
|
||||||
|
|
||||||
|
### Reusable Assets
|
||||||
|
- `lifespan` function in `backend/main.py:136` — already has a startup block with DB session, Redis check, and AI seed; the ES256 rotation check slots in as another startup task in the same block
|
||||||
|
- `SystemSettings` model + upsert pattern in `backend/services/ai_config.py` — exact pattern for reading and writing a key-value entry in `system_settings` at startup; copy this to avoid re-inventing the upsert
|
||||||
|
- `RefreshToken.revoked` column — already exists (migrations/0001); no schema change needed for bulk revoke
|
||||||
|
|
||||||
|
### Established Patterns
|
||||||
|
- Base64-encoded keys: `config.py` already handles `minio_secret_key` and similar string secrets from env — apply the same `base64.b64decode(settings.jwt_private_key).decode()` pattern before constructing the `EllipticCurvePrivateKey` for PyJWT
|
||||||
|
- Token hash: refresh tokens stored as `sha256(raw.encode()).hexdigest()` — unchanged by this phase; only the *access* token algorithm changes
|
||||||
|
- `create_refresh_token` in `services/auth.py` currently uses `timedelta(days=settings.refresh_token_expire_days)` — add a `remember_me` param that selects between hours and days TTL
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- `backend/services/auth.py` — primary change site; 4 signing/decode functions + `create_refresh_token` TTL param
|
||||||
|
- `backend/config.py` — add 3 new settings; `secret_key` stays but JWT code stops referencing it
|
||||||
|
- `backend/main.py:lifespan` — add startup rotation check (5–10 lines)
|
||||||
|
- `backend/api/auth.py` — login endpoint gains `remember_me: bool = False`; `create_refresh_token` call passes it through
|
||||||
|
- `frontend/src/views/auth/LoginView.vue` — add checkbox, pass `remember_me` to store/API
|
||||||
|
- `frontend/src/stores/auth.js` (or `.ts`) — `login()` action gains `remember_me` param and passes it to the API
|
||||||
|
|
||||||
|
</code_context>
|
||||||
|
|
||||||
|
<specifics>
|
||||||
|
## Specific Ideas
|
||||||
|
|
||||||
|
- The startup rotation bulk UPDATE should use a raw SQL `UPDATE refresh_tokens SET revoked = true WHERE revoked = false` executed via `session.execute(text(...))` for efficiency — avoids loading all rows into Python (same pattern as other bulk updates in the codebase).
|
||||||
|
- The `system_settings` key for algorithm tracking: use `key="jwt_algorithm"`, `value="ES256"`, provider/model fields `NULL` or `""` (check existing SystemSettings schema for nullable constraints before inserting).
|
||||||
|
- PyJWT ES256 usage: `jwt.encode(payload, private_key_pem_str, algorithm="ES256")` and `jwt.decode(token, public_key_pem_str, algorithms=["ES256"])` — PyJWT accepts PEM strings directly; no need to use the `cryptography` key objects directly in the jwt calls.
|
||||||
|
- The "Remember me" checkbox label: "Stay signed in for 30 days" — clearer than "Remember me" about what it actually does.
|
||||||
|
|
||||||
|
</specifics>
|
||||||
|
|
||||||
|
<deferred>
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- **Phase 7.4 — Token fingerprinting / token binding**: Add `fgp` (fingerprint) claim = `hmac(key, User-Agent + Accept-Language)[:16]`; validate in `get_current_user`. `SECRET_KEY` may serve as the HMAC key here. Tracked in `.planning/codebase/CONCERNS.md` §"No Token Fingerprint / Token Binding".
|
||||||
|
- **Key rotation ceremony**: A process for rotating the P-256 key pair in production (dual-key overlap period, gradual rollout) — not needed for the initial upgrade but worth documenting when this goes to production.
|
||||||
|
|
||||||
|
</deferred>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 07.3-security-es256-algorithm-upgrade-inserted*
|
||||||
|
*Context gathered: 2026-06-05*
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
# Phase 7.3: Security — ES256 Algorithm Upgrade - Discussion Log
|
||||||
|
|
||||||
|
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||||
|
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||||
|
|
||||||
|
**Date:** 2026-06-05
|
||||||
|
**Phase:** 07.3-security-es256-algorithm-upgrade-inserted
|
||||||
|
**Areas discussed:** Key format in env vars, Startup refresh-token rotation, Key generation workflow, JWT TTL / Remember Me
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Format in Env Vars
|
||||||
|
|
||||||
|
### Q1: How should P-256 PEM keys be stored in env vars?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Base64-encoded | Single line, shell-safe, decoded in config.py | ✓ |
|
||||||
|
| Escaped `\n` in .env | python-dotenv expands `\n`; loader-dependent | |
|
||||||
|
| Multiline .env block | Breaks in docker-compose environment: block | |
|
||||||
|
|
||||||
|
**User's choice:** Base64-encoded
|
||||||
|
**Notes:** Standard 12-factor approach; no loader ambiguity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q2: Both keys required, or derive public from private at startup?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Both required env vars | Clean separation; workers can hold public key only | ✓ |
|
||||||
|
| Private key only — derive public | Fewer env vars but more magic in config.py | |
|
||||||
|
|
||||||
|
**User's choice:** Both required env vars
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q3: What happens to SECRET_KEY after ES256?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Remove from JWT code | SECRET_KEY no longer used for signing; stays in env for future HMAC | ✓ |
|
||||||
|
| Repurpose as HMAC key for Phase 7.4 | Forward-compatible but couples phases | |
|
||||||
|
|
||||||
|
**User's choice:** Remove SECRET_KEY from JWT code
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Startup Refresh-Token Rotation
|
||||||
|
|
||||||
|
### Q1: How to implement startup rotation?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| DELETE all refresh_tokens | Simple, one-shot; no schema change | |
|
||||||
|
| Bulk revoke (set revoked=True) | Cleaner audit trail; same user impact | ✓ |
|
||||||
|
| Let natural expiry handle it | Doesn't satisfy ROADMAP requirement | |
|
||||||
|
|
||||||
|
**User's choice:** Bulk revoke (set revoked=True)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q2: How to detect "first boot after ES256 migration"?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Check system_settings table | Stores `jwt_algorithm`; idempotent on restart | ✓ |
|
||||||
|
| New env var JWT_TOKEN_GENERATION=N | Simple but requires operator bump on deploy | |
|
||||||
|
| One-time Alembic migration | Runs at migrate time, not startup | |
|
||||||
|
|
||||||
|
**User's choice:** Check system_settings table (`jwt_algorithm` key)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Generation Workflow
|
||||||
|
|
||||||
|
### Q1: How should developers generate the key pair?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Python one-liner in README | Uses existing `cryptography` dep; no extra tooling | ✓ |
|
||||||
|
| openssl CLI command | Familiar to ops but multi-step, requires openssl | |
|
||||||
|
| Auto-generate on first boot | Convenient for dev; dangerous in prod (ephemeral keys) | |
|
||||||
|
|
||||||
|
**User's choice:** Python one-liner documented in README
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q2: docker-compose key placeholder vs. .env reference?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| .env file reference only | Follows existing ${SECRET_KEY} pattern | ✓ |
|
||||||
|
| Placeholder comment in docker-compose | Helpful but could mislead | |
|
||||||
|
|
||||||
|
**User's choice:** .env file reference only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## JWT TTL / Remember Me
|
||||||
|
|
||||||
|
### Q1 (freeform): What token should default to 12 hours?
|
||||||
|
|
||||||
|
User note: "JWT Tokens should only be valid for 12 hours and the user can opt-in to create a token which is valid for 30 days."
|
||||||
|
|
||||||
|
Asked for clarification on whether this applied to the access token or refresh token. User asked: "What do you think is the most secure way to handle tokens?"
|
||||||
|
|
||||||
|
**Claude's recommendation:** Keep access token at 15 min (CLAUDE.md requirement). Change refresh token default to 16–24 hours with explicit "remember me" opt-in for 30 days.
|
||||||
|
|
||||||
|
**User's decision:** 16 hours — covers a full workday; guarantees session is revoked overnight.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Q2: Fold "remember me" into Phase 7.3 or separate phase?
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Fold into Phase 7.3 | Already touching token creation code; incremental work | ✓ |
|
||||||
|
| Separate phase 7.5 | Keeps 7.3 purely as ES256 upgrade | |
|
||||||
|
|
||||||
|
**User's choice:** Fold into Phase 7.3
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Claude's Discretion
|
||||||
|
|
||||||
|
- PyJWT API form: `jwt.encode(payload, pem_str, algorithm="ES256")` — PEM strings passed directly, no key object conversion needed
|
||||||
|
- Bulk revoke via raw SQL `UPDATE` rather than ORM row-by-row — efficiency decision
|
||||||
|
- "Stay signed in for 30 days" as checkbox label (clearer than "Remember me")
|
||||||
|
- system_settings key name: `jwt_algorithm`, value `"ES256"`
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- **Phase 7.4:** Token fingerprinting / token binding (`fgp` claim = HMAC of User-Agent + Accept-Language)
|
||||||
|
- **Key rotation ceremony:** Dual-key overlap process for rotating P-256 keys in production — deferred until production hardening milestone
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
---
|
||||||
|
milestone: "0.1"
|
||||||
|
audited: "2026-06-05T00:00:00Z"
|
||||||
|
status: gaps_found
|
||||||
|
scores:
|
||||||
|
requirements: 53/54
|
||||||
|
phases: 9/9
|
||||||
|
integration: 53/54
|
||||||
|
flows: 5/6
|
||||||
|
gaps:
|
||||||
|
requirements:
|
||||||
|
- id: "AUTH-07"
|
||||||
|
status: "partial"
|
||||||
|
phase: "2"
|
||||||
|
claimed_by_plans: ["02-01-PLAN.md", "02-02-PLAN.md", "02-03-PLAN.md"]
|
||||||
|
completed_by_plans: ["02-03-PLAN.md (sign-out-all, password-reset-confirm)"]
|
||||||
|
verification_status: "gaps_found"
|
||||||
|
evidence: >
|
||||||
|
AUTH-07 requires refresh token family revocation on all privilege-change events.
|
||||||
|
sign_out_all (auth.py:402) and password_reset_confirm (auth.py:698) correctly call
|
||||||
|
revoke_all_refresh_tokens(). Three handlers do NOT:
|
||||||
|
CR-01: change_password (~line 493) — commits without revoke;
|
||||||
|
CR-02: disable_totp (~line 614) — commits without revoke;
|
||||||
|
CR-03: enable_totp (~line 580) — commits without revoke.
|
||||||
|
All three violate CLAUDE.md security invariant: "Password change, TOTP enroll/revoke,
|
||||||
|
and account deactivation immediately revoke all active sessions."
|
||||||
|
integration:
|
||||||
|
- "CR-01: change_password (auth.py ~line 493) does not call revoke_all_refresh_tokens() after commit"
|
||||||
|
- "CR-02: disable_totp (auth.py ~line 614) does not call revoke_all_refresh_tokens() after commit"
|
||||||
|
- "CR-03: enable_totp (auth.py ~line 580) does not call revoke_all_refresh_tokens() after commit"
|
||||||
|
flows:
|
||||||
|
- "Privilege-change session revocation broken: password change, TOTP enable, TOTP disable do not invalidate existing sessions"
|
||||||
|
tech_debt:
|
||||||
|
- phase: "06.1-close-v1-audit-gaps"
|
||||||
|
items:
|
||||||
|
- "STORE-06: test_delete_decrements_quota is xfail(strict=False) on SQLite — INTEGRATION=1 confirmation under live PostgreSQL never recorded"
|
||||||
|
- "Audit filter behavioral tests: test_audit_log_viewer verifies shape only; no test passes ?event_type=X or ?user_id=Y and asserts narrowed results"
|
||||||
|
- phase: "06-performance-production-hardening"
|
||||||
|
items:
|
||||||
|
- "VALIDATION.md stuck at 'draft' / nyquist_compliant: false — planning artifact, never updated after waves executed"
|
||||||
|
- "No VERIFICATION.md — 5 phases lack formal phase-level verification files (1, 3, 4, 6, 7)"
|
||||||
|
- phase: "07-redo-and-optimize-llm-integration"
|
||||||
|
items:
|
||||||
|
- "VALIDATION.md stuck at 'draft' / nyquist_compliant: false — planning artifact, never updated after waves executed"
|
||||||
|
- "Enhancement noted in UAT test 3: model name field could be searchable dropdown. Tracked as future improvement."
|
||||||
|
- phase: "01-infrastructure-foundation"
|
||||||
|
items:
|
||||||
|
- "UAT file shows status: testing / 0 passed — was never completed; Phase 1 implementation is fully functional per subsequent phase dependencies"
|
||||||
|
- phase: "02-users-authentication"
|
||||||
|
items:
|
||||||
|
- "WR-04: topicsStore.fetchTopics() fires unconditionally in App.vue onMounted — causes spurious 401+refresh on auth pages"
|
||||||
|
- "WR-03: ConfirmBlock.vue has no named slot — sign-out-all spinner guard never activates"
|
||||||
|
- "WR-01: decodeURIComponent in SettingsView.vue onMounted lacks try/catch — URIError on malformed %encoding"
|
||||||
|
nyquist:
|
||||||
|
compliant_phases: [1, 2, 3, 4, 5, "6.1", "6.2"]
|
||||||
|
partial_phases: [6, 7]
|
||||||
|
missing_phases: []
|
||||||
|
overall: "PARTIAL — phases 6 and 7 have stale draft VALIDATION.md files; actual test files exist and pass"
|
||||||
|
---
|
||||||
|
|
||||||
|
# v0.1 Milestone Audit Report
|
||||||
|
|
||||||
|
**Milestone:** v0.1 — Foundation & Alpha
|
||||||
|
**Audited:** 2026-06-05
|
||||||
|
**Status:** ⚠ GAPS FOUND
|
||||||
|
**Score:** 53/54 requirements satisfied · 3 security blockers · 1 partial (STORE-06 integration test)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase Verification Coverage
|
||||||
|
|
||||||
|
| Phase | VERIFICATION.md | Status | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1. Infrastructure Foundation | ❌ Missing | UAT: 0/7 (template, never completed) | Phase functionally complete — all subsequent phases depend on it |
|
||||||
|
| 2. Users & Authentication | ✅ Present | human_needed, 6/6 must-haves | 3 SECURITY BLOCKERS (CR-01, CR-02, CR-03) open |
|
||||||
|
| 3. Document Migration | ❌ Missing | UAT: 10/10 passed | No formal VERIFICATION.md |
|
||||||
|
| 4. Folders, Sharing, Quotas | ❌ Missing | UAT: 14/15 passed | No formal VERIFICATION.md |
|
||||||
|
| 5. Cloud Storage Backends | ✅ Present | human_needed, 7/7 must-haves | All CLOUD-* satisfied |
|
||||||
|
| 6. Performance & Production | ❌ Missing | UAT: 8/8 passed | No formal VERIFICATION.md |
|
||||||
|
| 6.1. Close v1 Audit Gaps | ✅ Present | gaps_found, 9/11 | STORE-06 integration test unconfirmed |
|
||||||
|
| 6.2. Close v1 Sharing/CSV | ✅ Present | human_needed, 5/5 | All must-haves verified |
|
||||||
|
| 7. Redo LLM Integration | ❌ Missing | UAT: 11/11 passed | No formal VERIFICATION.md |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Blockers (3) — MUST FIX before milestone close
|
||||||
|
|
||||||
|
All three blockers violate the same CLAUDE.md security invariant:
|
||||||
|
> "Password change, TOTP enroll/revoke, and account deactivation immediately revoke all active sessions."
|
||||||
|
|
||||||
|
### CR-01 — `change_password` does not revoke sessions
|
||||||
|
**File:** `backend/api/auth.py` — `change_password` handler (~line 493)
|
||||||
|
**Issue:** `await session.commit()` returns `{"message": "Password updated"}` without calling `revoke_all_refresh_tokens()`. A stolen refresh cookie remains valid for 30 days after the victim changes their password.
|
||||||
|
**Fix:** Add `await auth_service.revoke_all_refresh_tokens(session, current_user.id)` before `await session.commit()` at line 493.
|
||||||
|
**Affected requirement:** AUTH-07
|
||||||
|
|
||||||
|
### CR-02 — `disable_totp` does not revoke sessions
|
||||||
|
**File:** `backend/api/auth.py` — `disable_totp` handler (~line 614)
|
||||||
|
**Issue:** TOTP disable is a privilege-level downgrade. Existing sessions were issued under the assumption TOTP was required; they must be revoked on disable.
|
||||||
|
**Fix:** Add `await auth_service.revoke_all_refresh_tokens(session, current_user.id)` before `await session.commit()` at line 614.
|
||||||
|
**Affected requirement:** AUTH-07
|
||||||
|
|
||||||
|
### CR-03 — `enable_totp` does not revoke sessions
|
||||||
|
**File:** `backend/api/auth.py` — `enable_totp` handler (~line 580)
|
||||||
|
**Issue:** TOTP enrollment is a security-level upgrade. Pre-enrollment sessions bypass the TOTP gate — this is a session fixation / 2FA bypass vector.
|
||||||
|
**Fix:** Add `await auth_service.revoke_all_refresh_tokens(session, current_user.id)` before `await session.commit()` at line 580.
|
||||||
|
**Affected requirement:** AUTH-07
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements Coverage (3-Source Cross-Reference)
|
||||||
|
|
||||||
|
| REQ-ID | REQUIREMENTS.md | VERIFICATION.md | SUMMARY Frontmatter | Final Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| AUTH-01 | `[x]` | Phase 2: SATISFIED | 02-01: listed | ✅ satisfied |
|
||||||
|
| AUTH-02 | `[x]` | Phase 2: SATISFIED | 02-01: listed | ✅ satisfied |
|
||||||
|
| AUTH-03 | `[ ]` stale | Phase 2: SATISFIED | 02-03: listed | ✅ satisfied (checkbox stale) |
|
||||||
|
| AUTH-04 | `[x]` | Phase 2: SATISFIED | 02-03: listed | ✅ satisfied |
|
||||||
|
| AUTH-05 | `[ ]` stale | Phase 2: SATISFIED | 02-03: listed | ✅ satisfied (checkbox stale) |
|
||||||
|
| AUTH-06 | `[ ]` stale | Phase 2: sign_out_all wired | 02-03: listed | ✅ satisfied (checkbox stale) |
|
||||||
|
| **AUTH-07** | `[ ]` stale | 3 handlers skip revoke | 02-01, 02-03: listed | ⚠️ **partial — 3 blockers** |
|
||||||
|
| AUTH-08 | `[ ]` stale | Phase 2: Redis used-code TTL | 02-03: listed | ✅ satisfied (checkbox stale) |
|
||||||
|
| SEC-01 | `[x]` | Phase 2: OriginValidationMiddleware | — | ✅ satisfied |
|
||||||
|
| SEC-02 | `[x]` | Phase 2: slowapi + account_limiter | — | ✅ satisfied |
|
||||||
|
| SEC-03 | `[x]` | Phase 2: ORM-only | — | ✅ satisfied |
|
||||||
|
| SEC-04 | `[x]` | Phase 3: DB-lookup only | — | ✅ satisfied |
|
||||||
|
| SEC-05 | `[x]` | SecurityHeadersMiddleware | — | ✅ satisfied |
|
||||||
|
| SEC-06 | `[ ]` stale | 02-01: hmac.compare_digest | 02-01: listed | ✅ satisfied (checkbox stale) |
|
||||||
|
| SEC-07 | `[ ]` stale | 02-04: admin blocked | 02-04: listed | ✅ satisfied (checkbox stale) |
|
||||||
|
| SEC-08 | `[ ]` stale | Phase 5: CloudConnectionOut | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| SEC-09 | `[x]` | Phase 5: VERIFIED | — | ✅ satisfied |
|
||||||
|
| ADMIN-01 | `[x]` | Phase 2: SATISFIED | 02-04, 02-05: listed | ✅ satisfied |
|
||||||
|
| ADMIN-02 | `[x]` | Phase 2: SATISFIED | 02-04: listed | ✅ satisfied |
|
||||||
|
| ADMIN-03 | `[x]` | Phase 2: SATISFIED | 02-04: listed | ✅ satisfied |
|
||||||
|
| ADMIN-04 | `[x]` | Phase 2: SATISFIED | 02-04: listed | ✅ satisfied |
|
||||||
|
| ADMIN-05 | `[x]` | Phase 2: SATISFIED | 02-04: listed | ✅ satisfied |
|
||||||
|
| ADMIN-06 | `[ ]` stale | Phase 6.2: SATISFIED | 06.1-02: listed | ✅ satisfied (checkbox stale) |
|
||||||
|
| ADMIN-07 | `[x]` | Phase 2: SATISFIED | — | ✅ satisfied |
|
||||||
|
| STORE-01 | `[ ]` stale | Phase 1: PostgreSQL+MinIO wired | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| STORE-02 | `[ ]` stale | Phase 1: UUID key enforced | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| STORE-03 | `[ ]` stale | Phase 3: atomic UPDATE | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| STORE-04 | `[ ]` stale | Phase 3: QuotaBar.vue | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| STORE-05 | `[ ]` stale | Phase 3: HTTP 413 + detail | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| STORE-06 | `[ ]` stale | Phase 6.1: PARTIAL | — | ⚠️ partial (INTEGRATION=1 unconfirmed) |
|
||||||
|
| STORE-07 | `[ ]` stale | Phase 1: Celery+Redis, no file locks | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| STORE-08 | `[ ]` stale | Phase 1: extract_and_classify.delay() | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| FOLD-01 | `[x]` | Phase 4: folders CRUD | — | ✅ satisfied |
|
||||||
|
| FOLD-02 | `[x]` | Phase 4: document move | — | ✅ satisfied |
|
||||||
|
| FOLD-03 | `[x]` | Phase 4: breadcrumb | — | ✅ satisfied |
|
||||||
|
| FOLD-04 | `[x]` | Phase 4: sort | — | ✅ satisfied |
|
||||||
|
| FOLD-05 | `[x]` | Phase 4: FTS tsvector | — | ✅ satisfied |
|
||||||
|
| SHARE-01 | `[ ]` stale | Phase 6.1: SATISFIED | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| SHARE-02 | `[ ]` stale | Phase 6.1: SATISFIED | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| SHARE-03 | `[ ]` stale | Phase 6.2: SATISFIED | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| SHARE-04 | `[ ]` stale | Phase 6.1: SATISFIED | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| SHARE-05 | `[ ]` stale | Phase 6.2: SATISFIED | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| CLOUD-01 | `[x]` | Phase 5: SATISFIED | — | ✅ satisfied |
|
||||||
|
| CLOUD-02 | `[x]` | Phase 5: SATISFIED | — | ✅ satisfied |
|
||||||
|
| CLOUD-03 | `[x]` | Phase 5: SATISFIED | — | ✅ satisfied |
|
||||||
|
| CLOUD-04 | `[x]` | Phase 5: SATISFIED | — | ✅ satisfied |
|
||||||
|
| CLOUD-05 | `[x]` | Phase 5: SATISFIED | — | ✅ satisfied |
|
||||||
|
| CLOUD-06 | `[x]` | Phase 5: SATISFIED | — | ✅ satisfied |
|
||||||
|
| CLOUD-07 | `[x]` | Phase 5: SATISFIED | — | ✅ satisfied |
|
||||||
|
| DOC-01 | `[ ]` stale | Phase 4: DocumentView.vue | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| DOC-02 | `[ ]` stale | Phase 4: /content proxy | — | ✅ satisfied (checkbox stale) |
|
||||||
|
| DOC-03 | `[x]` | Phase 3: per-user AI | — | ✅ satisfied |
|
||||||
|
| DOC-04 | `[x]` | Phase 3: topic namespacing | — | ✅ satisfied |
|
||||||
|
| DOC-05 | `[x]` | Phase 3: Celery DB lookup | — | ✅ satisfied |
|
||||||
|
|
||||||
|
**REQUIREMENTS.md note:** 30/54 checkboxes are `[x]`. The remaining 24 `[ ]` are stale — the file was last updated 2026-05-21 before phases ran. All satisfied requirements above should have their checkboxes updated in the archive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Nyquist Compliance
|
||||||
|
|
||||||
|
| Phase | VALIDATION.md | Compliant | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | ✅ exists | ✅ true | compliant |
|
||||||
|
| 2 | ✅ exists | ✅ true | validated |
|
||||||
|
| 3 | ✅ exists | ✅ true | compliant |
|
||||||
|
| 4 | ✅ exists | ✅ true | complete |
|
||||||
|
| 5 | ✅ exists | ✅ true | complete |
|
||||||
|
| 6 | ✅ exists | ❌ false | Stale draft — Wave 0 tasks show ⬜ pending but tests exist and pass (06-01 through 06-06 summaries confirm). Run `/gsd:validate-phase 6` to update. |
|
||||||
|
| 6.1 | ✅ exists | ✅ true | validated |
|
||||||
|
| 6.2 | ✅ exists | ✅ true | complete |
|
||||||
|
| 7 | ✅ exists | ❌ false | Stale draft — same pattern as Phase 6. Run `/gsd:validate-phase 7` to update. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tech Debt Summary
|
||||||
|
|
||||||
|
| Phase | Item | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| 6.1 | STORE-06 integration gate (xfail under SQLite, INTEGRATION=1 unconfirmed) | Medium |
|
||||||
|
| 6.1 | Audit filter behavioral test missing (no test verifies filter params narrow results) | Low |
|
||||||
|
| 2 | WR-04: topicsStore.fetchTopics() fires on auth pages → spurious 401 | Low |
|
||||||
|
| 2 | WR-03: ConfirmBlock no named slot → sign-out-all spinner guard dead | Low |
|
||||||
|
| 2 | WR-01: decodeURIComponent without try/catch in SettingsView.vue | Low |
|
||||||
|
| 7 | Enhancement: model name field could be searchable dropdown | Cosmetic |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Audited: 2026-06-05_
|
||||||
|
_Auditor: Claude (gsd-audit-milestone)_
|
||||||
|
_Status: gaps_found — 3 security blockers require resolution before milestone close_
|
||||||
@@ -0,0 +1,349 @@
|
|||||||
|
# DocuVault
|
||||||
|
|
||||||
|
**Version 0.1.0 — Alpha**
|
||||||
|
|
||||||
|
> **Not production-ready.** DocuVault is functional for local and self-hosted use but has not been audited or hardened for public internet exposure. APIs, environment variables, and the database schema may change without notice until a stable 1.0 release is declared.
|
||||||
|
|
||||||
|
A self-hosted, multi-user document management platform with AI-powered topic classification, pluggable cloud storage backends, and a security-first architecture.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Document management** — upload PDF, DOCX, images, and plain text; full-text extraction stored in PostgreSQL
|
||||||
|
- **AI classification** — automatic topic tagging via LM Studio (local), Ollama, OpenAI, Anthropic, or any OpenAI-compatible provider (Groq, xAI, DeepSeek, OpenRouter, Gemini, Mistral)
|
||||||
|
- **Folder organisation** — hierarchical folders with move, rename, and delete; breadcrumb navigation
|
||||||
|
- **Document sharing** — share by user handle with view or edit permission; "Shared with me" virtual folder; per-recipient revocation
|
||||||
|
- **Storage quota** — per-user limit enforced atomically; amber/red quota bar at 80 % / 95 %; quota decremented on delete
|
||||||
|
- **Cloud storage backends** — connect OneDrive, Google Drive, Nextcloud, or any WebDAV server as a personal storage backend; credentials encrypted with HKDF per-user keys
|
||||||
|
- **Auth** — email/password registration with HIBP breach check, optional TOTP 2FA, 8–10 backup codes, password reset by email, sign-out-all-devices
|
||||||
|
- **Admin panel** — user CRUD, quota assignment, per-user AI provider override, AI provider system configuration, audit log viewer with CSV export
|
||||||
|
- **Observability** — structured JSON logging via structlog, per-request correlation IDs, Loki + Promtail + Grafana stack included in Docker Compose
|
||||||
|
- **Production hardening** — non-root containers, read-only filesystems, CSP/CSRF/Origin-validation middleware, per-IP and per-account rate limiting, CVE scanning gate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ Vue 3 frontend (Vite + Pinia + Tailwind) │
|
||||||
|
│ Port 5173 │
|
||||||
|
└────────────────┬────────────────────────┘
|
||||||
|
│ REST / httpOnly cookie
|
||||||
|
┌────────────────▼────────────────────────┐
|
||||||
|
│ FastAPI backend (Python 3.12) │
|
||||||
|
│ Port 8000 │
|
||||||
|
│ ├── api/auth.py (JWT + TOTP) │
|
||||||
|
│ ├── api/documents.py │
|
||||||
|
│ ├── api/folders.py │
|
||||||
|
│ ├── api/shares.py │
|
||||||
|
│ ├── api/cloud.py (cloud backends) │
|
||||||
|
│ ├── api/admin.py │
|
||||||
|
│ └── api/audit.py │
|
||||||
|
└──┬──────────┬──────────┬───────────────┘
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
PostgreSQL MinIO Redis
|
||||||
|
(data) (objects) (rate limit / TOTP replay)
|
||||||
|
│
|
||||||
|
└── Celery worker
|
||||||
|
(extract + classify tasks)
|
||||||
|
Celery beat
|
||||||
|
(daily audit export)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Storage object key schema:** `{user_id}/{document_id}/{uuid4()}{ext}` — human filenames live in PostgreSQL only.
|
||||||
|
|
||||||
|
**Token flow:** Access token (15 min, ES256) in Pinia memory only. Refresh token (30 day) in an httpOnly `SameSite=Strict` cookie. Refresh rotation on every use; reuse revokes the entire token family.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
| Layer | Technology |
|
||||||
|
|-------|------------|
|
||||||
|
| Backend | Python 3.12, FastAPI 0.136+, SQLAlchemy 2.0 async, psycopg v3, Alembic |
|
||||||
|
| Task queue | Celery 5 + Redis, celery-beat for scheduled tasks |
|
||||||
|
| Object store | MinIO (S3-compatible) |
|
||||||
|
| Database | PostgreSQL 17 |
|
||||||
|
| Frontend | Vue 3 (Options API), Pinia, Vue Router 4, Vite, Tailwind CSS |
|
||||||
|
| Auth | PyJWT (ES256), pwdlib[argon2], pyotp (TOTP), cryptography (HKDF/AES-GCM) |
|
||||||
|
| AI providers | Anthropic, OpenAI, Ollama, LM Studio, GenericOpenAI (Groq/xAI/DeepSeek/Gemini/…) |
|
||||||
|
| Cloud backends | Google Drive, OneDrive (MSAL), Nextcloud, WebDAV |
|
||||||
|
| Observability | structlog, Loki, Promtail, Grafana |
|
||||||
|
| Rate limiting | slowapi (per-IP + per-account) |
|
||||||
|
| Load testing | Locust (`backend/load_tests/`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Docker and Docker Compose v2
|
||||||
|
- `openssl` (for generating secrets)
|
||||||
|
- *(Optional)* LM Studio or Ollama running on the host for local AI classification
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone
|
||||||
|
git clone <repo-url>
|
||||||
|
cd document_scanner
|
||||||
|
|
||||||
|
# 2. Create .env from the example
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# 3. Generate required secrets
|
||||||
|
python3 -c "import secrets; print(secrets.token_hex(64))" # → SECRET_KEY
|
||||||
|
python3 -c "import secrets; print(secrets.token_urlsafe(32))" # → CLOUD_CREDS_KEY
|
||||||
|
|
||||||
|
# 4. Edit .env — at minimum set:
|
||||||
|
# SECRET_KEY, CLOUD_CREDS_KEY, ADMIN_EMAIL, ADMIN_PASSWORD
|
||||||
|
# (everything else has usable defaults for local dev)
|
||||||
|
nano .env
|
||||||
|
|
||||||
|
# 5. Start all services
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# 6. Run database migrations
|
||||||
|
docker compose exec backend alembic upgrade head
|
||||||
|
|
||||||
|
# 7. Open the app
|
||||||
|
open http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
The bootstrap admin account (email + password from `.env`) is created automatically on first startup if no users exist.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Service URLs
|
||||||
|
|
||||||
|
| Service | URL | Notes |
|
||||||
|
|---------|-----|-------|
|
||||||
|
| Vue frontend | http://localhost:5173 | Main app |
|
||||||
|
| FastAPI backend | http://localhost:8000 | REST API |
|
||||||
|
| API health | http://localhost:8000/health | Postgres + MinIO probe |
|
||||||
|
| OpenAPI docs | http://localhost:8000/docs | Swagger UI (dev) |
|
||||||
|
| MinIO console | http://localhost:9001 | Object browser |
|
||||||
|
| Grafana | http://localhost:3000 | Log dashboard (admin/changeme) |
|
||||||
|
| Loki | http://localhost:3100/ready | Log aggregator |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env`. Only the fields marked **Required** must be set before first boot. See `RUNBOOK.md` for the full reference.
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
| Variable | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `SECRET_KEY` | JWT signing secret — generate with `openssl rand -hex 32` |
|
||||||
|
| `CLOUD_CREDS_KEY` | Master key for cloud credential encryption — generate with `openssl rand -hex 16` (pad to 32 chars) |
|
||||||
|
| `ADMIN_EMAIL` | Bootstrap admin email |
|
||||||
|
| `ADMIN_PASSWORD` | Bootstrap admin password (must pass strength check) |
|
||||||
|
| `POSTGRES_PASSWORD` | PostgreSQL superuser password |
|
||||||
|
| `MINIO_ROOT_PASSWORD` | MinIO root password |
|
||||||
|
| `REDIS_PASSWORD` | Redis `requirepass` password |
|
||||||
|
|
||||||
|
### Optional (sensible defaults for local dev)
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `CORS_ORIGINS` | `http://localhost:5173` | Comma-separated allowed origins |
|
||||||
|
| `SMTP_HOST` | *(unset)* | Leave empty to log password-reset links to stdout |
|
||||||
|
| `LOG_JSON` | `false` | Set `true` in production for structured JSON logs |
|
||||||
|
| `DEFAULT_AI_PROVIDER` | `ollama` | Used on first boot seed; overridable per-user in admin panel |
|
||||||
|
| `GOOGLE_CLIENT_ID/SECRET` | *(unset)* | Required only if using Google Drive backend |
|
||||||
|
| `ONEDRIVE_CLIENT_ID/SECRET` | *(unset)* | Required only if using OneDrive backend |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Backend (local, no Docker)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
pip install -r requirements.txt -r requirements-dev.txt
|
||||||
|
# Requires a running PostgreSQL and Redis — use docker compose up postgres redis minio
|
||||||
|
uvicorn main:app --reload --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend (local, no Docker)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run dev # http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database migrations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply all pending migrations
|
||||||
|
docker compose exec backend alembic upgrade head
|
||||||
|
|
||||||
|
# Create a new migration after changing db/models.py
|
||||||
|
docker compose exec backend alembic revision --autogenerate -m "describe change"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Live reload in Docker
|
||||||
|
|
||||||
|
`docker-compose.yml` mounts `./backend:/app` so Python changes reload automatically. After changing `requirements.txt` or `Dockerfile`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build backend celery-worker celery-beat
|
||||||
|
docker compose up -d backend celery-worker celery-beat
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Backend — all tests
|
||||||
|
cd backend && pytest -v
|
||||||
|
|
||||||
|
# Backend — integration tests (requires live PostgreSQL + MinIO)
|
||||||
|
INTEGRATION=1 pytest -v
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
cd frontend && npm run test
|
||||||
|
|
||||||
|
# Load testing (requires a running stack + load-test user)
|
||||||
|
cd backend
|
||||||
|
pip install locust
|
||||||
|
locust -f load_tests/locustfile.py --host http://localhost:8000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test coverage by layer
|
||||||
|
|
||||||
|
| Layer | Approach |
|
||||||
|
|-------|----------|
|
||||||
|
| Service / business logic | Unit tests with mocked dependencies |
|
||||||
|
| DB queries | Integration tests against real PostgreSQL |
|
||||||
|
| API endpoints | `httpx.AsyncClient` against a real DB |
|
||||||
|
| Auth flows | Full round-trip (register → login → TOTP → refresh → revoke) |
|
||||||
|
| Security invariants | Negative tests (wrong owner → 403/404, admin block, token replay) |
|
||||||
|
| Frontend | Vitest unit tests in `frontend/tests/` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AI Providers
|
||||||
|
|
||||||
|
AI provider settings live in the `system_settings` database table and are managed through the Admin → AI Providers panel. API keys are encrypted at rest with HKDF/AES-GCM.
|
||||||
|
|
||||||
|
| Provider | Type | `provider_id` |
|
||||||
|
|----------|------|---------------|
|
||||||
|
| LM Studio | Local (OpenAI-compat) | `lmstudio` |
|
||||||
|
| Ollama | Local (OpenAI-compat) | `ollama` |
|
||||||
|
| OpenAI | Cloud | `openai` |
|
||||||
|
| Anthropic | Cloud (native) | `anthropic` |
|
||||||
|
| Groq | Cloud (OpenAI-compat) | `groq` |
|
||||||
|
| xAI Grok | Cloud (OpenAI-compat) | `xai` |
|
||||||
|
| DeepSeek | Cloud (OpenAI-compat) | `deepseek` |
|
||||||
|
| OpenRouter | Cloud (OpenAI-compat) | `openrouter` |
|
||||||
|
| Gemini (compat) | Cloud | `gemini` |
|
||||||
|
| Mistral (compat) | Cloud | `mistral` |
|
||||||
|
|
||||||
|
Documents whose classification fails are automatically retried by Celery at 30 s, 90 s, and 270 s. After three failures the document shows a red "Classification failed" badge with a "Re-analyze" button.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cloud Storage Backends
|
||||||
|
|
||||||
|
Users connect cloud storage through **Settings → Cloud Storage**. Credentials are encrypted per-user with an HKDF-derived key and never returned in API responses.
|
||||||
|
|
||||||
|
| Provider | Auth method | Notes |
|
||||||
|
|----------|-------------|-------|
|
||||||
|
| Google Drive | OAuth 2.0 | Requires `GOOGLE_CLIENT_ID/SECRET` |
|
||||||
|
| Microsoft OneDrive | OAuth 2.0 (MSAL) | Requires `ONEDRIVE_CLIENT_ID/SECRET` |
|
||||||
|
| Nextcloud | Username + password | Custom server URL; SSRF allowlist enforced |
|
||||||
|
| WebDAV | Username + password | Any RFC 4918 server; SSRF allowlist enforced |
|
||||||
|
|
||||||
|
Connection statuses: `ACTIVE`, `REQUIRES_REAUTH`, `ERROR`. An externally revoked OAuth token transitions to `REQUIRES_REAUTH` without a 500 error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Highlights
|
||||||
|
|
||||||
|
- **Passwords** hashed with Argon2id; HIBP breach check on registration and password change
|
||||||
|
- **JWT** signed with ES256 (ECDSA P-256); access token 15 min; refresh token 30 days in httpOnly `SameSite=Strict` cookie
|
||||||
|
- **Token fingerprinting** — access token embeds HMAC of `User-Agent + Accept-Language`; validated on every request
|
||||||
|
- **TOTP replay prevention** — used codes marked in Redis within the 90 s validity window
|
||||||
|
- **Quota** enforced by a single atomic `UPDATE … RETURNING` — no read-then-write race
|
||||||
|
- **IDOR protection** — every resource endpoint asserts `resource.user_id == current_user.id`
|
||||||
|
- **SSRF prevention** — user-supplied WebDAV/Nextcloud URLs pass a hostname allowlist
|
||||||
|
- **CSP / security headers** on every response
|
||||||
|
- **Container hardening** — non-root user (`appuser` uid 1000), read-only root filesystem, all Linux capabilities dropped
|
||||||
|
- **Audit log** — all auth events, quota violations, and admin actions logged without document content
|
||||||
|
|
||||||
|
Security gate (run before every production deploy):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bandit -r backend/ # zero HIGH
|
||||||
|
pip audit # zero critical/high CVEs
|
||||||
|
npm audit --audit-level=high # zero high/critical
|
||||||
|
docker scout cves local://docuvault-backend:latest --only-severity critical --exit-code
|
||||||
|
```
|
||||||
|
|
||||||
|
See `SECURITY.md` for the full threat model and `RUNBOOK.md` for operational procedures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
Structured JSON logs are emitted to stdout and collected by Promtail → Loki → Grafana.
|
||||||
|
|
||||||
|
Every request gets an `X-Correlation-ID` header bound to all log lines for that request. Log fields include `correlation_id`, `path`, `method`, `status_code`, `duration_ms`, and `user_id` (when authenticated).
|
||||||
|
|
||||||
|
Access Grafana at http://localhost:3000 (default credentials: `admin` / `changeme` — change via `GRAFANA_ADMIN_PASSWORD` in `.env`). Add a Loki datasource pointing to `http://loki:3100` to query logs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── backend/
|
||||||
|
│ ├── ai/ # Provider implementations + classifier
|
||||||
|
│ ├── api/ # FastAPI routers (auth, documents, folders, shares, cloud, admin, audit)
|
||||||
|
│ ├── db/ # SQLAlchemy models + async session
|
||||||
|
│ ├── deps/ # FastAPI dependency injection (auth, db, utils)
|
||||||
|
│ ├── migrations/ # Alembic migration versions
|
||||||
|
│ ├── services/ # Business logic (auth, classifier, storage, email, audit, rate limiting)
|
||||||
|
│ ├── storage/ # StorageBackend ABC + MinIO/Google/OneDrive/Nextcloud/WebDAV backends
|
||||||
|
│ ├── tasks/ # Celery tasks (document processing, email, audit export)
|
||||||
|
│ ├── load_tests/ # Locust load test scripts
|
||||||
|
│ ├── tests/ # pytest test suite
|
||||||
|
│ ├── main.py # FastAPI application factory + middleware
|
||||||
|
│ └── config.py # Pydantic settings
|
||||||
|
├── frontend/
|
||||||
|
│ └── src/
|
||||||
|
│ ├── api/ # Axios API client modules
|
||||||
|
│ ├── components/ # UI components (documents, folders, cloud, admin, sharing, layout)
|
||||||
|
│ ├── stores/ # Pinia stores (auth, documents, folders, cloud connections)
|
||||||
|
│ ├── views/ # Page-level view components
|
||||||
|
│ └── utils/ # formatters.js (formatDate, formatSize, providerColor, …)
|
||||||
|
├── docker/
|
||||||
|
│ ├── postgres/ # initdb.d — user + role bootstrap SQL
|
||||||
|
│ └── loki/ # Loki + Promtail config
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── .env.example
|
||||||
|
├── RUNBOOK.md # Operational runbook (env vars, backup, on-call, failure modes)
|
||||||
|
└── SECURITY.md # Threat model and security gate results
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. All new endpoints require at least one integration test
|
||||||
|
2. Run `pytest -v` and `npm run test` — zero failures required
|
||||||
|
3. Run `bandit -r backend/` — zero HIGH findings
|
||||||
|
4. No raw SQL string interpolation; no `pickle`; no `eval`; no `innerHTML` with user data
|
||||||
|
5. Service layer raises `ValueError` (never `HTTPException`); only the router layer raises `HTTPException`
|
||||||
|
6. Before writing a new helper, check the shared module map in `CLAUDE.md`
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Count,Message,Traceback,Nodes
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
Method,Name,Error,Occurrences
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
Type,Name,Request Count,Failure Count,Median Response Time,Average Response Time,Min Response Time,Max Response Time,Average Content Size,Requests/s,Failures/s,50%,66%,75%,80%,90%,95%,98%,99%,99.9%,99.99%,100%
|
||||||
|
GET,GET /api/documents/,8113,0,26,38.175937513619786,13.075874999969983,2100.3851669999563,5805.175274251202,11.281529163417646,0.0,26,30,33,35,45,60,110,280,2000,2100,2100
|
||||||
|
GET,GET /api/documents/ (for get_document),4012,0,26,35.98545581505448,12.926040999957422,1984.6321670000293,5805.171734795613,5.578885123090299,0.0,26,30,33,35,43,57,90,230,1900,2000,2000
|
||||||
|
GET,GET /api/documents/{id},4012,0,4,4.788209801345967,1.9728749999785578,844.3694160000064,269.0,5.578885123090299,0.0,4,4,4,5,6,7,11,21,110,840,840
|
||||||
|
POST,POST /api/documents/upload,2678,0,6,10.145406270352133,2.943457999947441,1684.5705000000066,472.0,3.7238919141664564,0.0,6,6,7,7,9,13,31,93,1400,1700,1700
|
||||||
|
,Aggregated,18815,0,22,26.5997728138718,1.9728749999785578,2100.3851669999563,3865.5849056603774,26.163191323764703,0.0,22,26,28,31,38,50,81,150,1900,2100,2100
|
||||||
|
@@ -0,0 +1,717 @@
|
|||||||
|
Timestamp,User Count,Type,Name,Requests/s,Failures/s,50%,66%,75%,80%,90%,95%,98%,99%,99.9%,99.99%,100%,Total Request Count,Total Failure Count,Total Median Response Time,Total Average Response Time,Total Min Response Time,Total Max Response Time,Total Average Content Size
|
||||||
|
1780656365,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656366,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656367,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656368,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656369,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656370,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656371,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656372,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656373,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656374,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656375,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656376,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656377,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656378,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656379,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656380,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656381,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656382,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656383,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656384,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656385,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656386,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656387,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656388,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656389,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656390,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656391,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656392,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656393,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656394,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656395,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656396,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656397,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656398,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656399,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656400,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656401,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656402,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656403,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656404,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656405,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656406,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656407,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656408,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656409,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656410,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656411,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656412,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656413,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656414,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656415,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656416,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656417,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656418,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656419,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656420,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656421,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656422,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656423,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656424,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656425,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656426,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656427,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656428,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656429,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656430,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656431,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656432,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656433,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656434,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656435,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656436,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656437,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656438,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656439,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656440,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656441,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656442,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656443,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656444,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656445,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656446,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656447,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656448,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656449,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656450,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656451,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656452,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656453,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656454,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656455,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656456,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656457,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656458,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656459,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656460,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656461,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656462,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656463,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656464,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656465,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656466,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656467,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656468,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656469,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656470,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656471,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656472,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656473,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656474,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656475,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656476,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656477,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656478,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656479,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656480,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656481,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656482,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656483,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656484,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656485,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656486,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656487,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656488,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656489,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656490,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656491,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656492,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656493,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656494,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656495,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656496,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656497,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656498,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656499,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656500,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656501,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656502,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656503,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656504,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656505,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656506,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656507,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656508,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656509,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656510,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656511,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656512,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656513,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656514,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656515,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656516,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656518,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656519,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656520,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656521,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656522,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656523,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656524,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656525,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656526,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656527,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656528,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656529,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656530,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656531,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656532,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656533,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656534,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656535,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656536,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656537,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656538,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656539,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656540,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656541,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656542,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656543,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656544,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656545,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656546,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656547,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656548,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656549,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656550,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656551,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656552,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656553,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656554,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656555,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656556,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656557,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656558,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656559,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656560,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656561,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656562,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656563,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656564,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656565,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656566,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656567,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656568,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656569,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656570,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656571,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656572,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656573,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656574,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656575,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656576,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656577,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656578,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656579,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656580,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656581,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656582,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656583,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656584,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656585,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656586,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656587,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656588,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656589,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656590,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656591,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656592,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656593,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656594,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656595,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656596,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656597,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656598,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656599,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656600,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656601,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656602,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656603,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656604,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656605,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656606,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656607,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656608,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656609,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656610,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656611,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656612,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656613,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656614,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656615,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656616,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656617,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656618,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656619,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656620,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656621,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656622,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656623,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656624,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656625,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656626,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656627,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656628,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656629,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656630,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656631,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656632,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656633,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656634,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656635,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656636,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656637,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656638,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656639,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656640,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656641,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656642,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656643,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656644,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656645,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656646,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656647,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656648,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656649,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656650,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656651,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656652,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656653,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656654,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656655,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656656,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656657,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656658,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656659,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656660,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656661,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656662,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656663,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656664,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656665,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656666,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656667,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656668,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656669,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656670,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656671,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656672,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656673,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656674,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656675,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656676,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656677,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656678,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656679,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656680,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656681,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656682,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656683,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656684,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656685,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656686,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656687,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656688,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656689,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656690,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656691,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656692,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656693,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656694,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656695,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656696,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656697,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656698,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656699,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656700,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656701,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656702,0,,Aggregated,0.000000,0.000000,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,0,0,0,0.0,0,0,0
|
||||||
|
1780656703,10,,Aggregated,0.000000,0.000000,150,200,200,200,200,200,200,200,200,200,200,10,0,150.0,155.3602791000003,49.83145900001773,204.981250000003,4738.4
|
||||||
|
1780656704,20,,Aggregated,0.000000,0.000000,57,61,150,150,200,200,200,200,200,200,200,30,0,57,74.73139433333245,3.5707920000049853,204.981250000003,3993.5
|
||||||
|
1780656705,30,,Aggregated,0.000000,0.000000,41,58,82,91,150,200,200,200,200,200,200,58,0,31,56.27858255171982,3.342207999992297,204.981250000003,3655.189655172414
|
||||||
|
1780656706,40,,Aggregated,1.000000,0.000000,27,43,61,86,98,150,200,200,200,200,200,106,0,25,45.62888160377097,2.8799589999835007,204.981250000003,3696.2735849056603
|
||||||
|
1780656707,50,,Aggregated,3.300000,0.000000,25,41,59,83,100,150,200,200,200,200,200,155,0,25,42.75801444516161,2.8799589999835007,204.981250000003,3799.303225806452
|
||||||
|
1780656708,50,,Aggregated,6.500000,0.000000,24,33,45,58,95,120,150,200,200,200,200,203,0,24,37.38136157635623,2.8799589999835007,204.981250000003,3871.4926108374384
|
||||||
|
1780656709,50,,Aggregated,11.700000,0.000000,21,27,34,43,87,110,150,200,200,200,200,262,0,21,32.13861793129841,2.8799589999835007,204.981250000003,3765.2748091603053
|
||||||
|
1780656710,50,,Aggregated,16.600000,0.000000,22,28,38,55,100,150,250,420,460,460,460,295,0,22,41.04692154237303,2.8799589999835007,463.003915999991,3736.227118644068
|
||||||
|
1780656711,50,,Aggregated,21.600000,0.000000,22,31,45,66,130,220,360,460,580,580,580,360,0,22,51.88309983333413,2.8799589999835007,581.9329999999923,3774.8277777777776
|
||||||
|
1780656712,50,,Aggregated,27.000000,0.000000,22,29,38,57,120,200,340,460,580,580,580,410,0,22,48.007316712195475,2.3616659999561307,581.9329999999923,3848.3634146341465
|
||||||
|
1780656713,50,,Aggregated,32.600000,0.000000,22,29,36,47,110,200,330,460,580,580,580,458,0,22,45.21672229694382,2.3616659999561307,581.9329999999923,3863.1310043668122
|
||||||
|
1780656714,50,,Aggregated,37.500000,0.000000,22,29,37,44,98,200,320,420,580,580,580,508,0,22,43.52781156102433,2.3616659999561307,581.9329999999923,3903.2913385826773
|
||||||
|
1780656715,50,,Aggregated,42.400000,0.000000,22,29,37,44,98,170,320,420,580,580,580,542,0,22,42.970889343173894,2.3616659999561307,581.9329999999923,3892.4261992619927
|
||||||
|
1780656716,50,,Aggregated,46.100000,0.000000,21,29,37,44,100,200,320,420,580,580,580,600,0,21,43.4865166333335,2.3616659999561307,581.9329999999923,3895.4816666666666
|
||||||
|
1780656717,50,,Aggregated,48.700000,0.000000,21,27,33,42,95,170,300,390,580,580,580,653,0,21,41.24417506278707,2.3616659999561307,581.9329999999923,3891.252679938744
|
||||||
|
1780656718,50,,Aggregated,49.500000,0.000000,21,27,32,40,90,160,300,360,580,580,580,701,0,21,39.56023107417978,2.3616659999561307,581.9329999999923,3898.833095577746
|
||||||
|
1780656719,50,,Aggregated,49.400000,0.000000,20,26,31,37,87,150,300,360,580,580,580,749,0,20,37.938395558077296,2.3616659999561307,581.9329999999923,3896.966622162884
|
||||||
|
1780656720,50,,Aggregated,50.100000,0.000000,20,25,30,34,82,150,270,340,580,580,580,804,0,20,36.36999432462694,2.3616659999561307,581.9329999999923,3891.042288557214
|
||||||
|
1780656721,50,,Aggregated,49.300000,0.000000,20,25,29,33,74,130,270,340,580,580,580,864,0,20,34.93960906365731,2.3616659999561307,581.9329999999923,3884.402777777778
|
||||||
|
1780656722,50,,Aggregated,49.400000,0.000000,20,24,28,31,67,130,260,340,580,580,580,912,0,20,33.87566682346467,2.3616659999561307,581.9329999999923,3860.0164473684213
|
||||||
|
1780656723,50,,Aggregated,49.100000,0.000000,19,24,28,31,61,120,250,340,580,580,580,965,0,19,32.886596130569615,2.3616659999561307,581.9329999999923,3864.839378238342
|
||||||
|
1780656724,50,,Aggregated,49.600000,0.000000,19,24,28,31,58,120,240,330,470,580,580,1007,0,19,32.277007879841115,2.3616659999561307,581.9329999999923,3870.6087388282026
|
||||||
|
1780656725,50,,Aggregated,50.100000,0.000000,19,23,27,30,55,120,220,330,470,580,580,1055,0,19,31.548481493838786,2.3616659999561307,581.9329999999923,3871.1450236966825
|
||||||
|
1780656726,50,,Aggregated,50.200000,0.000000,19,23,27,30,53,110,210,320,470,580,580,1111,0,19,30.93307464446421,2.3616659999561307,581.9329999999923,3860.2763276327632
|
||||||
|
1780656727,50,,Aggregated,49.500000,0.000000,19,23,27,30,50,100,210,320,470,580,580,1162,0,19,30.326385623923972,2.3616659999561307,581.9329999999923,3851.5688468158346
|
||||||
|
1780656728,50,,Aggregated,50.200000,0.000000,19,23,27,29,48,100,200,320,470,580,580,1209,0,19,29.778513451612376,2.3616659999561307,581.9329999999923,3851.344913151365
|
||||||
|
1780656729,50,,Aggregated,51.700000,0.000000,19,23,26,29,47,97,200,320,470,580,580,1266,0,19,29.237436358609,2.3616659999561307,581.9329999999923,3840.013428120063
|
||||||
|
1780656730,50,,Aggregated,50.100000,0.000000,19,23,26,29,45,95,200,300,470,580,580,1315,0,19,28.801868324714167,2.3616659999561307,581.9329999999923,3851.0114068441067
|
||||||
|
1780656731,50,,Aggregated,50.700000,0.000000,19,23,26,29,44,93,200,300,470,580,580,1363,0,19,28.377748049155493,2.3616659999561307,581.9329999999923,3855.433602347762
|
||||||
|
1780656732,50,,Aggregated,51.000000,0.000000,19,22,26,28,43,90,200,300,470,580,580,1414,0,19,27.97016514144178,2.3616659999561307,581.9329999999923,3867.8847241867043
|
||||||
|
1780656733,50,,Aggregated,50.600000,0.000000,19,22,26,28,43,89,200,300,470,580,580,1464,0,19,27.694119366802425,2.3616659999561307,581.9329999999923,3856.0198087431695
|
||||||
|
1780656734,50,,Aggregated,49.600000,0.000000,19,22,26,28,42,88,200,280,470,580,580,1518,0,19,27.275941567192895,2.3616659999561307,581.9329999999923,3864.423583662714
|
||||||
|
1780656735,50,,Aggregated,50.000000,0.000000,19,22,25,28,41,86,180,280,470,580,580,1564,0,19,26.965516283247364,2.3616659999561307,581.9329999999923,3872.4635549872123
|
||||||
|
1780656736,50,,Aggregated,50.100000,0.000000,19,22,25,28,40,85,170,270,470,580,580,1615,0,19,26.556604854488402,2.3616659999561307,581.9329999999923,3866.1907120743035
|
||||||
|
1780656737,50,,Aggregated,50.700000,0.000000,19,22,25,27,40,82,170,270,470,580,580,1665,0,19,26.282685416215422,2.3616659999561307,581.9329999999923,3881.798798798799
|
||||||
|
1780656738,50,,Aggregated,51.600000,0.000000,19,22,25,27,39,78,160,270,470,580,580,1720,0,19,25.92424893837124,2.183625000043321,581.9329999999923,3888.8162790697675
|
||||||
|
1780656739,50,,Aggregated,49.300000,0.000000,19,22,25,27,38,75,160,270,470,580,580,1768,0,19,25.659412361424327,2.183625000043321,581.9329999999923,3891.543552036199
|
||||||
|
1780656740,50,,Aggregated,51.200000,0.000000,19,22,24,27,37,71,150,260,470,580,580,1821,0,19,25.357811694672378,2.183625000043321,581.9329999999923,3878.092806150467
|
||||||
|
1780656741,50,,Aggregated,51.100000,0.000000,19,22,24,27,37,74,150,260,470,580,580,1875,0,19,25.295970363199146,1.9728749999785578,581.9329999999923,3872.2341333333334
|
||||||
|
1780656742,50,,Aggregated,50.500000,0.000000,19,22,25,28,41,91,200,320,470,580,580,1922,0,19,28.269499053589236,1.9728749999785578,581.9329999999923,3871.271071800208
|
||||||
|
1780656743,50,,Aggregated,50.300000,0.000000,19,22,25,27,40,89,200,320,470,580,580,1986,0,19,27.844191849948878,1.9728749999785578,581.9329999999923,3867.401812688822
|
||||||
|
1780656744,50,,Aggregated,51.200000,0.000000,19,22,25,27,40,89,200,310,460,580,580,2038,0,19,27.556669085376992,1.9728749999785578,581.9329999999923,3857.7767419038273
|
||||||
|
1780656745,50,,Aggregated,50.800000,0.000000,19,22,25,27,39,87,200,310,460,580,580,2085,0,19,27.294572967864944,1.9728749999785578,581.9329999999923,3867.6407673860913
|
||||||
|
1780656746,50,,Aggregated,51.900000,0.000000,19,22,25,27,39,86,200,310,460,580,580,2135,0,19,27.037839448711185,1.9728749999785578,581.9329999999923,3866.814051522248
|
||||||
|
1780656747,50,,Aggregated,52.100000,0.000000,19,22,25,27,39,83,200,310,460,580,580,2187,0,19,26.832329700959473,1.9728749999785578,581.9329999999923,3865.5454961133973
|
||||||
|
1780656748,50,,Aggregated,51.700000,0.000000,19,22,25,27,39,81,200,310,460,580,580,2233,0,19,26.658546618001928,1.9728749999785578,581.9329999999923,3866.3770712046576
|
||||||
|
1780656749,50,,Aggregated,51.800000,0.000000,19,22,25,27,39,76,200,310,460,580,580,2280,0,19,26.47047401842044,1.9728749999785578,581.9329999999923,3868.381140350877
|
||||||
|
1780656750,50,,Aggregated,51.600000,0.000000,19,22,24,27,38,74,180,300,460,580,580,2335,0,19,26.15521113918564,1.9728749999785578,581.9329999999923,3858.4873661670235
|
||||||
|
1780656751,50,,Aggregated,51.400000,0.000000,18,22,24,26,38,72,170,300,460,580,580,2395,0,18,25.88850056075075,1.9728749999785578,581.9329999999923,3847.661795407098
|
||||||
|
1780656752,50,,Aggregated,50.500000,0.000000,18,22,24,27,38,70,170,300,460,580,580,2438,0,18,25.77233972887524,1.9728749999785578,581.9329999999923,3846.6021328958163
|
||||||
|
1780656753,50,,Aggregated,51.100000,0.000000,18,22,24,26,37,69,170,300,460,580,580,2487,0,18,25.58520592480824,1.9728749999785578,581.9329999999923,3852.205870526739
|
||||||
|
1780656754,50,,Aggregated,52.000000,0.000000,18,22,24,26,37,67,160,300,460,580,580,2539,0,18,25.395183509254853,1.9728749999785578,581.9329999999923,3851.332414336353
|
||||||
|
1780656755,50,,Aggregated,51.000000,0.000000,18,22,24,26,36,66,160,300,460,580,580,2598,0,18,25.16626483910617,1.9728749999785578,581.9329999999923,3849.445727482679
|
||||||
|
1780656756,50,,Aggregated,49.700000,0.000000,18,22,24,26,36,64,160,290,460,580,580,2641,0,18,25.035519361226044,1.9728749999785578,581.9329999999923,3850.227186671715
|
||||||
|
1780656757,50,,Aggregated,49.700000,0.000000,18,22,24,26,35,64,160,290,460,580,580,2698,0,18,24.832030242030374,1.9728749999785578,581.9329999999923,3850.8636026686436
|
||||||
|
1780656758,50,,Aggregated,50.300000,0.000000,18,22,24,26,35,63,150,280,460,580,580,2747,0,18,24.7235801215864,1.9728749999785578,581.9329999999923,3857.802329814343
|
||||||
|
1780656759,50,,Aggregated,51.000000,0.000000,18,22,24,26,35,61,150,280,460,580,580,2794,0,18,24.649560743019972,1.9728749999785578,581.9329999999923,3863.399427344309
|
||||||
|
1780656760,50,,Aggregated,50.700000,0.000000,18,22,24,26,35,59,150,280,460,580,580,2847,0,18,24.536665688091865,1.9728749999785578,581.9329999999923,3866.9870038637164
|
||||||
|
1780656761,50,,Aggregated,50.900000,0.000000,19,22,24,26,35,58,150,280,460,580,580,2895,0,19,24.44533006804761,1.9728749999785578,581.9329999999923,3874.470811744387
|
||||||
|
1780656762,50,,Aggregated,51.300000,0.000000,19,22,24,26,34,58,150,270,460,580,580,2945,0,19,24.295946973174097,1.9728749999785578,581.9329999999923,3872.0825127334465
|
||||||
|
1780656763,50,,Aggregated,51.400000,0.000000,19,22,24,26,35,58,140,270,460,580,580,2997,0,19,24.25940925725645,1.9728749999785578,581.9329999999923,3871.065398732065
|
||||||
|
1780656764,50,,Aggregated,49.600000,0.000000,19,22,24,26,34,58,140,270,460,580,580,3055,0,19,24.091518604254578,1.9728749999785578,581.9329999999923,3866.701145662848
|
||||||
|
1780656765,50,,Aggregated,50.500000,0.000000,19,22,24,26,34,57,140,270,460,580,580,3100,0,19,24.018436720966964,1.9728749999785578,581.9329999999923,3869.9674193548385
|
||||||
|
1780656766,50,,Aggregated,50.800000,0.000000,19,22,24,26,34,55,130,260,460,580,580,3150,0,19,23.96153169206279,1.9728749999785578,581.9329999999923,3860.711746031746
|
||||||
|
1780656767,50,,Aggregated,51.700000,0.000000,19,22,24,27,37,69,200,290,460,580,580,3211,0,19,25.723940657426866,1.9728749999785578,581.9329999999923,3861.821550918717
|
||||||
|
1780656768,50,,Aggregated,50.900000,0.000000,19,22,25,27,37,67,200,290,460,580,580,3249,0,19,25.64972797783869,1.9728749999785578,581.9329999999923,3862.835333948907
|
||||||
|
1780656769,50,,Aggregated,50.100000,0.000000,19,22,24,27,37,66,180,290,460,580,580,3306,0,19,25.52665496823902,1.9728749999785578,581.9329999999923,3864.934664246824
|
||||||
|
1780656770,50,,Aggregated,50.800000,0.000000,19,22,24,27,36,65,170,290,460,580,580,3354,0,19,25.403251202742467,1.9728749999785578,581.9329999999923,3870.015205724508
|
||||||
|
1780656771,50,,Aggregated,50.100000,0.000000,19,22,24,26,36,64,170,280,460,580,580,3402,0,19,25.289895622574498,1.9728749999785578,581.9329999999923,3876.5796590241034
|
||||||
|
1780656773,50,,Aggregated,50.900000,0.000000,19,22,24,26,36,64,170,280,460,580,580,3446,0,19,25.243580331688445,1.9728749999785578,581.9329999999923,3877.5174114915844
|
||||||
|
1780656774,50,,Aggregated,50.700000,0.000000,19,22,24,26,36,64,170,280,460,580,580,3495,0,19,25.111537944491676,1.9728749999785578,581.9329999999923,3872.032331902718
|
||||||
|
1780656775,50,,Aggregated,50.700000,0.000000,19,22,24,26,35,63,170,280,460,580,580,3540,0,19,25.015980094067373,1.9728749999785578,581.9329999999923,3868.741525423729
|
||||||
|
1780656776,50,,Aggregated,49.800000,0.000000,19,22,24,26,36,63,160,280,460,580,580,3600,0,19,24.964297000555113,1.9728749999785578,581.9329999999923,3866.3766666666666
|
||||||
|
1780656777,50,,Aggregated,48.300000,0.000000,19,22,24,26,36,63,160,280,460,580,580,3639,0,19,24.885532046166134,1.9728749999785578,581.9329999999923,3867.7111843913162
|
||||||
|
1780656778,50,,Aggregated,49.300000,0.000000,19,22,24,27,36,61,160,280,460,580,580,3690,0,19,24.7965440035227,1.9728749999785578,581.9329999999923,3866.421680216802
|
||||||
|
1780656779,50,,Aggregated,48.800000,0.000000,19,22,24,26,35,60,160,280,460,580,580,3738,0,19,24.69775821321524,1.9728749999785578,581.9329999999923,3869.480203317282
|
||||||
|
1780656780,50,,Aggregated,47.800000,0.000000,19,22,24,27,35,60,150,280,460,580,580,3788,0,19,24.679314092396698,1.9728749999785578,581.9329999999923,3874.782206969377
|
||||||
|
1780656781,50,,Aggregated,48.600000,0.000000,19,22,24,27,35,60,150,270,460,580,580,3849,0,19,24.58716248480086,1.9728749999785578,581.9329999999923,3868.5045466354895
|
||||||
|
1780656782,50,,Aggregated,47.800000,0.000000,19,22,25,27,36,62,150,270,460,580,580,3897,0,19,24.66289539774146,1.9728749999785578,581.9329999999923,3872.729022324865
|
||||||
|
1780656783,50,,Aggregated,49.200000,0.000000,19,22,25,27,36,60,150,270,460,580,580,3942,0,19,24.59638081126295,1.9728749999785578,581.9329999999923,3876.7876712328766
|
||||||
|
1780656784,50,,Aggregated,49.000000,0.000000,19,22,25,27,37,66,180,300,460,580,580,3983,0,19,25.77165042907321,1.9728749999785578,581.9329999999923,3885.7717800652777
|
||||||
|
1780656785,50,,Aggregated,49.300000,0.000000,19,22,25,27,37,65,170,300,460,580,580,4026,0,19,25.692023552905738,1.9728749999785578,581.9329999999923,3878.0998509687033
|
||||||
|
1780656786,50,,Aggregated,48.400000,0.000000,19,22,25,27,37,64,170,300,460,580,580,4086,0,19,25.598251864904157,1.9728749999785578,581.9329999999923,3881.0996084189915
|
||||||
|
1780656787,50,,Aggregated,48.200000,0.000000,19,22,25,27,37,64,170,290,460,580,580,4126,0,19,25.534219500726678,1.9728749999785578,581.9329999999923,3886.3829374697043
|
||||||
|
1780656788,50,,Aggregated,48.300000,0.000000,19,22,25,27,37,64,170,290,460,580,580,4179,0,19,25.44870912897777,1.9728749999785578,581.9329999999923,3887.307968413496
|
||||||
|
1780656789,50,,Aggregated,48.700000,0.000000,19,22,25,27,37,63,170,290,460,580,580,4224,0,19,25.374763335463562,1.9728749999785578,581.9329999999923,3885.8901515151515
|
||||||
|
1780656790,50,,Aggregated,48.700000,0.000000,19,22,25,27,37,63,160,290,460,580,580,4273,0,19,25.287450287853503,1.9728749999785578,581.9329999999923,3888.701146735315
|
||||||
|
1780656791,50,,Aggregated,48.400000,0.000000,19,22,25,27,37,62,160,290,460,580,580,4317,0,19,25.216626041926833,1.9728749999785578,581.9329999999923,3886.6203381978225
|
||||||
|
1780656792,50,,Aggregated,48.800000,0.000000,19,22,25,27,36,60,160,290,460,580,580,4372,0,19,25.119785546431412,1.9728749999785578,581.9329999999923,3885.8467520585546
|
||||||
|
1780656793,50,,Aggregated,46.600000,0.000000,19,22,25,27,37,59,160,290,460,580,580,4419,0,19,25.063064331522494,1.9728749999785578,581.9329999999923,3886.489929848382
|
||||||
|
1780656794,50,,Aggregated,47.700000,0.000000,19,22,25,27,36,58,150,290,460,580,580,4471,0,19,24.99299709952986,1.9728749999785578,581.9329999999923,3886.7879668977857
|
||||||
|
1780656795,50,,Aggregated,47.700000,0.000000,19,22,25,27,37,62,150,280,460,580,580,4508,0,19,25.16148051419661,1.9728749999785578,581.9329999999923,3882.0155279503106
|
||||||
|
1780656796,50,,Aggregated,48.900000,0.000000,19,22,25,27,38,66,200,300,460,580,580,4566,0,19,26.09647118988137,1.9728749999785578,581.9329999999923,3883.6727989487517
|
||||||
|
1780656797,50,,Aggregated,48.300000,0.000000,19,22,25,27,38,65,190,300,460,580,580,4607,0,19,26.03854903776828,1.9728749999785578,581.9329999999923,3887.817885825917
|
||||||
|
1780656798,50,,Aggregated,48.000000,0.000000,19,23,25,27,38,65,190,300,460,580,580,4654,0,19,25.983311435109336,1.9728749999785578,581.9329999999923,3887.350021486893
|
||||||
|
1780656799,50,,Aggregated,47.600000,0.000000,19,23,25,27,38,65,190,300,460,580,580,4699,0,19,26.013720446477674,1.9728749999785578,581.9329999999923,3891.7065333049586
|
||||||
|
1780656800,50,,Aggregated,47.300000,0.000000,19,23,25,27,38,64,190,300,460,580,580,4747,0,19,25.92103025574019,1.9728749999785578,581.9329999999923,3894.8544343796084
|
||||||
|
1780656801,50,,Aggregated,47.200000,0.000000,19,23,25,27,38,63,180,300,460,580,580,4794,0,19,25.833483460158256,1.9728749999785578,581.9329999999923,3891.979349186483
|
||||||
|
1780656802,50,,Aggregated,46.700000,0.000000,19,23,25,27,38,63,170,290,460,580,580,4841,0,19,25.74154167527352,1.9728749999785578,581.9329999999923,3888.184259450527
|
||||||
|
1780656803,50,,Aggregated,47.700000,0.000000,19,23,25,27,38,63,170,290,460,580,580,4893,0,19,25.628986596566328,1.9728749999785578,581.9329999999923,3880.7676272225626
|
||||||
|
1780656804,50,,Aggregated,46.600000,0.000000,19,22,25,27,37,62,170,290,460,580,580,4941,0,19,25.537380844565654,1.9728749999785578,581.9329999999923,3882.901032179721
|
||||||
|
1780656805,50,,Aggregated,47.100000,0.000000,19,22,25,27,37,60,170,290,460,580,580,4995,0,19,25.454359585184946,1.9728749999785578,581.9329999999923,3884.015415415415
|
||||||
|
1780656806,50,,Aggregated,47.300000,0.000000,19,22,25,27,37,60,170,290,460,580,580,5043,0,19,25.375330071385868,1.9728749999785578,581.9329999999923,3882.82173309538
|
||||||
|
1780656807,50,,Aggregated,48.400000,0.000000,19,22,25,27,37,59,160,290,460,580,580,5093,0,19,25.28436142096976,1.9728749999785578,581.9329999999923,3879.2246220302377
|
||||||
|
1780656808,50,,Aggregated,47.400000,0.000000,19,22,25,27,37,59,160,290,460,580,580,5152,0,19,25.222411079774673,1.9728749999785578,581.9329999999923,3875.8835403726707
|
||||||
|
1780656809,50,,Aggregated,48.600000,0.000000,19,22,25,27,37,58,160,290,460,580,580,5203,0,19,25.18082710474716,1.9728749999785578,581.9329999999923,3875.9138958293292
|
||||||
|
1780656810,50,,Aggregated,49.500000,0.000000,19,22,25,27,37,58,160,290,460,580,580,5243,0,19,25.12702779305732,1.9728749999785578,581.9329999999923,3876.042532901011
|
||||||
|
1780656811,50,,Aggregated,49.800000,0.000000,19,22,25,27,37,58,160,290,460,580,580,5299,0,19,25.03366145593499,1.9728749999785578,581.9329999999923,3874.756746555954
|
||||||
|
1780656812,50,,Aggregated,49.900000,0.000000,19,23,25,27,37,57,150,280,460,580,580,5348,0,19,25.001502191473374,1.9728749999785578,581.9329999999923,3879.0991024682125
|
||||||
|
1780656813,50,,Aggregated,50.000000,0.000000,19,23,25,27,36,56,150,280,460,580,580,5402,0,19,24.92741517993332,1.9728749999785578,581.9329999999923,3880.1675305442427
|
||||||
|
1780656814,50,,Aggregated,50.000000,0.000000,19,23,25,27,36,56,150,280,460,580,580,5436,0,19,24.911149897718897,1.9728749999785578,581.9329999999923,3885.2271891096393
|
||||||
|
1780656815,50,,Aggregated,50.100000,0.000000,19,23,25,27,36,55,150,280,460,580,580,5492,0,19,24.85067816970133,1.9728749999785578,581.9329999999923,3882.8849235251273
|
||||||
|
1780656816,50,,Aggregated,49.000000,0.000000,19,23,25,27,36,55,150,280,460,580,580,5540,0,19,24.773181326714763,1.9728749999785578,581.9329999999923,3879.8462093862818
|
||||||
|
1780656817,50,,Aggregated,49.400000,0.000000,19,23,25,27,36,55,150,280,460,580,580,5593,0,19,24.702716121222835,1.9728749999785578,581.9329999999923,3878.547112462006
|
||||||
|
1780656818,50,,Aggregated,48.900000,0.000000,19,23,25,27,36,54,150,280,460,580,580,5636,0,19,24.64434683552146,1.9728749999785578,581.9329999999923,3877.781050390348
|
||||||
|
1780656819,50,,Aggregated,49.500000,0.000000,19,23,25,27,36,54,150,280,460,580,580,5692,0,19,24.591270828882465,1.9728749999785578,581.9329999999923,3877.503865073788
|
||||||
|
1780656820,50,,Aggregated,48.500000,0.000000,19,23,25,27,36,54,150,270,460,580,580,5743,0,19,24.550310605606644,1.9728749999785578,581.9329999999923,3873.837889604736
|
||||||
|
1780656821,50,,Aggregated,49.600000,0.000000,19,23,25,27,36,54,150,270,460,580,580,5785,0,19,24.506700082281643,1.9728749999785578,581.9329999999923,3873.0032843560934
|
||||||
|
1780656822,50,,Aggregated,49.500000,0.000000,19,23,25,27,36,53,140,270,460,580,580,5838,0,19,24.448437232271225,1.9728749999785578,581.9329999999923,3873.682596779719
|
||||||
|
1780656823,50,,Aggregated,48.200000,0.000000,19,23,25,27,36,53,140,270,460,580,580,5890,0,19,24.392479085059307,1.9728749999785578,581.9329999999923,3874.090831918506
|
||||||
|
1780656824,50,,Aggregated,48.800000,0.000000,19,23,25,27,35,53,140,270,460,580,580,5940,0,19,24.32845902306386,1.9728749999785578,581.9329999999923,3872.9782828282828
|
||||||
|
1780656825,50,,Aggregated,49.400000,0.000000,19,23,25,27,35,52,140,270,460,580,580,5986,0,19,24.270128866020592,1.9728749999785578,581.9329999999923,3870.456899432008
|
||||||
|
1780656826,50,,Aggregated,50.300000,0.000000,19,23,25,27,35,52,140,270,440,580,580,6009,0,19,24.34094897520375,1.9728749999785578,581.9329999999923,3869.7726743218504
|
||||||
|
1780656827,50,,Aggregated,49.300000,0.000000,19,23,25,27,36,55,170,300,470,590,590,6079,0,19,25.520608101167866,1.9728749999785578,590.2735000000234,3871.445303503866
|
||||||
|
1780656828,50,,Aggregated,49.300000,0.000000,19,23,25,27,36,55,170,300,470,590,590,6093,0,19,25.512419296405618,1.9728749999785578,590.2735000000234,3872.320367634991
|
||||||
|
1780656829,50,,Aggregated,48.000000,0.000000,19,23,25,27,36,57,190,310,570,870,870,6105,0,19,26.335649635216946,1.9728749999785578,869.5124169999531,3873.465356265356
|
||||||
|
1780656830,50,,Aggregated,46.400000,0.000000,19,23,26,28,38,70,280,430,2000,2100,2100,6165,0,19,36.95051756253035,1.9728749999785578,2100.3851669999563,3875.432765612328
|
||||||
|
1780656831,50,,Aggregated,41.800000,0.000000,19,23,26,28,38,70,280,420,2000,2100,2100,6204,0,19,36.834211405705915,1.9728749999785578,2100.3851669999563,3873.5143455834946
|
||||||
|
1780656832,50,,Aggregated,40.000000,0.000000,19,23,26,28,38,69,280,420,2000,2100,2100,6253,0,19,36.70067476507269,1.9728749999785578,2100.3851669999563,3874.7767471613624
|
||||||
|
1780656833,50,,Aggregated,41.100000,0.000000,20,23,26,28,38,68,280,420,2000,2100,2100,6303,0,20,36.58780604013956,1.9728749999785578,2100.3851669999563,3877.0428367444074
|
||||||
|
1780656834,50,,Aggregated,40.600000,0.000000,20,23,26,28,38,69,270,420,2000,2100,2100,6359,0,20,36.46395752901395,1.9728749999785578,2100.3851669999563,3873.446768359805
|
||||||
|
1780656835,50,,Aggregated,39.900000,0.000000,20,23,26,28,38,68,270,420,2000,2100,2100,6411,0,20,36.35883270457021,1.9728749999785578,2100.3851669999563,3876.2561222898144
|
||||||
|
1780656836,50,,Aggregated,40.600000,0.000000,20,23,26,28,38,69,270,420,2000,2100,2100,6471,0,20,36.256621588317074,1.9728749999785578,2100.3851669999563,3875.569463761397
|
||||||
|
1780656837,50,,Aggregated,40.900000,0.000000,20,23,26,28,38,68,270,410,2000,2100,2100,6515,0,20,36.133457054643074,1.9728749999785578,2100.3851669999563,3876.0099769762087
|
||||||
|
1780656838,50,,Aggregated,44.000000,0.000000,20,23,26,28,38,69,260,410,2000,2100,2100,6576,0,20,36.012247725973175,1.9728749999785578,2100.3851669999563,3874.664689781022
|
||||||
|
1780656839,50,,Aggregated,42.300000,0.000000,20,23,26,28,38,68,260,410,2000,2100,2100,6619,0,20,35.9129254733343,1.9728749999785578,2100.3851669999563,3874.0682882610668
|
||||||
|
1780656840,50,,Aggregated,45.700000,0.000000,20,23,26,28,38,67,260,410,2000,2100,2100,6671,0,20,35.771226240743474,1.9728749999785578,2100.3851669999563,3871.1368610403238
|
||||||
|
1780656841,50,,Aggregated,49.700000,0.000000,20,23,26,28,38,67,260,410,2000,2100,2100,6719,0,20,35.64971684134541,1.9728749999785578,2100.3851669999563,3868.012204197053
|
||||||
|
1780656842,50,,Aggregated,51.500000,0.000000,20,23,26,28,38,67,250,410,2000,2100,2100,6753,0,20,35.56758313534723,1.9728749999785578,2100.3851669999563,3869.7472234562415
|
||||||
|
1780656843,50,,Aggregated,51.500000,0.000000,20,23,26,28,38,66,250,410,2000,2100,2100,6815,0,20,35.46327207057957,1.9728749999785578,2100.3851669999563,3869.6016140865736
|
||||||
|
1780656844,50,,Aggregated,51.400000,0.000000,20,23,26,28,38,65,250,410,2000,2100,2100,6867,0,20,35.30968843090143,1.9728749999785578,2100.3851669999563,3865.1751856705987
|
||||||
|
1780656845,50,,Aggregated,51.400000,0.000000,20,23,26,28,38,65,250,410,2000,2100,2100,6921,0,20,35.183323471030235,1.9728749999785578,2100.3851669999563,3864.5767952608003
|
||||||
|
1780656846,50,,Aggregated,50.600000,0.000000,20,23,26,28,38,65,250,410,2000,2100,2100,6977,0,20,35.045417507524775,1.9728749999785578,2100.3851669999563,3866.8079403755196
|
||||||
|
1780656847,50,,Aggregated,50.900000,0.000000,20,23,26,28,38,64,240,410,2000,2100,2100,7026,0,20,34.909460882152054,1.9728749999785578,2100.3851669999563,3866.2580415599205
|
||||||
|
1780656848,50,,Aggregated,50.700000,0.000000,20,23,26,28,38,63,240,410,2000,2100,2100,7083,0,20,34.76996501510668,1.9728749999785578,2100.3851669999563,3867.906113228858
|
||||||
|
1780656849,50,,Aggregated,50.800000,0.000000,20,23,26,28,37,63,240,400,2000,2100,2100,7131,0,20,34.66456438900585,1.9728749999785578,2100.3851669999563,3871.0521665965503
|
||||||
|
1780656850,50,,Aggregated,50.600000,0.000000,20,23,26,28,37,63,240,400,2000,2100,2100,7182,0,20,34.55379810470631,1.9728749999785578,2100.3851669999563,3872.650097465887
|
||||||
|
1780656851,50,,Aggregated,51.200000,0.000000,20,23,26,28,37,62,240,400,2000,2100,2100,7231,0,20,34.44435125197076,1.9728749999785578,2100.3851669999563,3870.6292352371734
|
||||||
|
1780656852,50,,Aggregated,51.100000,0.000000,20,23,26,28,37,62,240,400,2000,2100,2100,7277,0,20,34.341335598460944,1.9728749999785578,2100.3851669999563,3870.175209564381
|
||||||
|
1780656853,50,,Aggregated,50.500000,0.000000,20,23,26,28,37,61,230,400,2000,2100,2100,7327,0,20,34.22847273563537,1.9728749999785578,2100.3851669999563,3869.8893134980212
|
||||||
|
1780656854,50,,Aggregated,51.700000,0.000000,20,23,26,28,37,60,230,400,2000,2100,2100,7386,0,20,34.08651927958303,1.9728749999785578,2100.3851669999563,3866.884105063634
|
||||||
|
1780656855,50,,Aggregated,51.100000,0.000000,20,23,26,28,37,60,230,390,2000,2100,2100,7434,0,20,33.98462554143132,1.9728749999785578,2100.3851669999563,3864.804950228679
|
||||||
|
1780656856,50,,Aggregated,51.300000,0.000000,20,23,26,28,37,60,230,390,2000,2100,2100,7481,0,20,33.958818426547346,1.9728749999785578,2100.3851669999563,3868.990108274295
|
||||||
|
1780656857,50,,Aggregated,51.400000,0.000000,20,23,26,28,37,60,230,380,2000,2100,2100,7533,0,20,33.830188811628915,1.9728749999785578,2100.3851669999563,3867.1100491172174
|
||||||
|
1780656858,50,,Aggregated,50.700000,0.000000,20,23,26,28,37,60,220,380,2000,2100,2100,7591,0,20,33.70716053326313,1.9728749999785578,2100.3851669999563,3866.842576735608
|
||||||
|
1780656859,50,,Aggregated,50.400000,0.000000,20,23,26,28,37,59,220,380,2000,2100,2100,7634,0,20,33.62951864199637,1.9728749999785578,2100.3851669999563,3868.465417867435
|
||||||
|
1780656860,50,,Aggregated,50.600000,0.000000,20,23,26,28,37,59,220,380,2000,2100,2100,7683,0,20,33.54202902824422,1.9728749999785578,2100.3851669999563,3868.110373551998
|
||||||
|
1780656861,50,,Aggregated,49.400000,0.000000,20,23,26,28,37,58,210,380,2000,2100,2100,7741,0,20,33.42079726753655,1.9728749999785578,2100.3851669999563,3867.7880118847693
|
||||||
|
1780656862,50,,Aggregated,49.700000,0.000000,20,23,26,28,37,58,210,380,2000,2100,2100,7791,0,20,33.32348173892957,1.9728749999785578,2100.3851669999563,3866.928250545501
|
||||||
|
1780656863,50,,Aggregated,51.500000,0.000000,20,23,26,28,37,58,210,370,2000,2100,2100,7838,0,20,33.23462902130651,1.9728749999785578,2100.3851669999563,3867.482138300587
|
||||||
|
1780656864,50,,Aggregated,50.000000,0.000000,20,23,26,28,37,57,210,370,2000,2100,2100,7884,0,20,33.128168645738235,1.9728749999785578,2100.3851669999563,3865.0006341958397
|
||||||
|
1780656865,50,,Aggregated,50.500000,0.000000,20,23,26,28,37,57,210,370,2000,2100,2100,7944,0,20,33.03680456709469,1.9728749999785578,2100.3851669999563,3861.093026183283
|
||||||
|
1780656866,50,,Aggregated,50.100000,0.000000,20,23,26,28,37,57,200,370,2000,2100,2100,7988,0,20,32.977052910615974,1.9728749999785578,2100.3851669999563,3862.9944917376065
|
||||||
|
1780656867,50,,Aggregated,50.400000,0.000000,20,23,26,28,36,57,200,360,2000,2100,2100,8041,0,20,32.86526422721055,1.9728749999785578,2100.3851669999563,3863.553662479791
|
||||||
|
1780656868,50,,Aggregated,50.300000,0.000000,20,23,26,28,36,57,200,360,2000,2100,2100,8093,0,20,32.77320322921043,1.9728749999785578,2100.3851669999563,3863.181638452984
|
||||||
|
1780656869,50,,Aggregated,50.200000,0.000000,20,23,26,28,37,57,200,360,2000,2100,2100,8143,0,20,32.7018624363257,1.9728749999785578,2100.3851669999563,3862.3374677637235
|
||||||
|
1780656870,50,,Aggregated,49.900000,0.000000,20,23,26,28,36,56,200,360,2000,2100,2100,8202,0,20,32.58240129870766,1.9728749999785578,2100.3851669999563,3859.0352353084613
|
||||||
|
1780656871,50,,Aggregated,50.100000,0.000000,20,23,26,28,36,56,200,360,2000,2100,2100,8246,0,20,32.50686190177057,1.9728749999785578,2100.3851669999563,3858.1534077128304
|
||||||
|
1780656872,50,,Aggregated,50.800000,0.000000,20,23,26,28,36,55,200,360,2000,2100,2100,8296,0,20,32.4225638304002,1.9728749999785578,2100.3851669999563,3857.3551108968177
|
||||||
|
1780656873,50,,Aggregated,50.000000,0.000000,20,23,26,28,36,55,200,350,2000,2100,2100,8347,0,20,32.350983067209796,1.9728749999785578,2100.3851669999563,3856.1850964418354
|
||||||
|
1780656874,50,,Aggregated,50.500000,0.000000,20,23,26,28,36,55,200,350,2000,2100,2100,8395,0,20,32.26542802727817,1.9728749999785578,2100.3851669999563,3855.6514592019057
|
||||||
|
1780656875,50,,Aggregated,50.500000,0.000000,20,23,26,28,36,55,200,340,2000,2100,2100,8448,0,20,32.20805812038358,1.9728749999785578,2100.3851669999563,3855.646661931818
|
||||||
|
1780656876,50,,Aggregated,50.700000,0.000000,20,23,26,28,37,55,190,340,2000,2100,2100,8498,0,20,32.1714678749118,1.9728749999785578,2100.3851669999563,3856.8843257236995
|
||||||
|
1780656877,50,,Aggregated,51.000000,0.000000,20,23,26,28,37,55,190,340,2000,2100,2100,8546,0,20,32.09646665621344,1.9728749999785578,2100.3851669999563,3853.81242686637
|
||||||
|
1780656878,50,,Aggregated,50.500000,0.000000,20,23,26,28,36,55,190,340,2000,2100,2100,8600,0,20,32.01769210813951,1.9728749999785578,2100.3851669999563,3857.1938372093023
|
||||||
|
1780656879,50,,Aggregated,50.300000,0.000000,20,23,26,28,36,54,180,340,2000,2100,2100,8642,0,20,31.95626261802817,1.9728749999785578,2100.3851669999563,3857.7623235362184
|
||||||
|
1780656880,50,,Aggregated,50.100000,0.000000,20,23,26,28,36,54,170,340,2000,2100,2100,8690,0,20,31.884111263521227,1.9728749999785578,2100.3851669999563,3857.238089758343
|
||||||
|
1780656881,50,,Aggregated,50.700000,0.000000,20,23,26,28,36,54,170,340,2000,2100,2100,8740,0,20,31.79795677036609,1.9728749999785578,2100.3851669999563,3856.50881006865
|
||||||
|
1780656882,50,,Aggregated,49.600000,0.000000,20,23,26,28,36,53,170,340,2000,2100,2100,8794,0,20,31.69645424994315,1.9728749999785578,2100.3851669999563,3853.549806686377
|
||||||
|
1780656883,50,,Aggregated,49.700000,0.000000,20,23,26,28,36,53,170,340,2000,2100,2100,8847,0,20,31.618151199954795,1.9728749999785578,2100.3851669999563,3855.9690290493954
|
||||||
|
1780656884,50,,Aggregated,50.000000,0.000000,20,23,26,28,36,53,170,340,2000,2100,2100,8895,0,20,31.56070894221474,1.9728749999785578,2100.3851669999563,3856.5742551995504
|
||||||
|
1780656885,50,,Aggregated,49.900000,0.000000,20,23,26,28,36,53,170,340,2000,2100,2100,8947,0,20,31.516014516374245,1.9728749999785578,2100.3851669999563,3858.6853693975636
|
||||||
|
1780656886,50,,Aggregated,50.100000,0.000000,20,23,26,28,36,53,160,340,2000,2100,2100,9003,0,20,31.445732830278825,1.9728749999785578,2100.3851669999563,3857.4667333111183
|
||||||
|
1780656887,50,,Aggregated,49.800000,0.000000,20,23,26,28,36,52,160,340,2000,2100,2100,9050,0,20,31.380453872486186,1.9728749999785578,2100.3851669999563,3858.584861878453
|
||||||
|
1780656888,50,,Aggregated,49.700000,0.000000,20,23,26,28,36,52,160,340,2000,2100,2100,9095,0,20,31.317433681693224,1.9728749999785578,2100.3851669999563,3858.6772952171523
|
||||||
|
1780656889,50,,Aggregated,50.600000,0.000000,20,23,26,28,36,52,160,330,2000,2100,2100,9146,0,20,31.24664915012015,1.9728749999785578,2100.3851669999563,3858.812814345069
|
||||||
|
1780656890,50,,Aggregated,50.000000,0.000000,20,23,26,28,36,52,160,330,2000,2100,2100,9192,0,20,31.19710781603556,1.9728749999785578,2100.3851669999563,3861.949412532637
|
||||||
|
1780656891,50,,Aggregated,50.100000,0.000000,20,23,26,28,36,52,160,330,2000,2100,2100,9245,0,20,31.14179709323946,1.9728749999785578,2100.3851669999563,3858.9806381828016
|
||||||
|
1780656892,50,,Aggregated,50.400000,0.000000,20,23,26,28,36,52,160,330,2000,2100,2100,9298,0,20,31.088540089696536,1.9728749999785578,2100.3851669999563,3860.08259840826
|
||||||
|
1780656893,50,,Aggregated,49.500000,0.000000,20,23,26,28,36,52,150,330,2000,2100,2100,9342,0,20,31.026836553628616,1.9728749999785578,2100.3851669999563,3861.0336116463286
|
||||||
|
1780656894,50,,Aggregated,50.000000,0.000000,20,23,26,28,36,52,150,330,2000,2100,2100,9403,0,20,30.99332451504819,1.9728749999785578,2100.3851669999563,3860.86504307136
|
||||||
|
1780656895,50,,Aggregated,50.100000,0.000000,20,23,26,28,36,52,150,330,2000,2100,2100,9448,0,20,30.96018069093971,1.9728749999785578,2100.3851669999563,3862.572078746825
|
||||||
|
1780656896,50,,Aggregated,48.900000,0.000000,20,23,26,28,36,52,150,330,2000,2100,2100,9495,0,20,30.91356708804613,1.9728749999785578,2100.3851669999563,3864.0885729331226
|
||||||
|
1780656897,50,,Aggregated,50.000000,0.000000,20,23,26,28,36,52,150,320,2000,2100,2100,9545,0,20,30.859474799266458,1.9728749999785578,2100.3851669999563,3863.9861707700366
|
||||||
|
1780656898,50,,Aggregated,49.900000,0.000000,20,23,26,28,36,52,150,320,2000,2100,2100,9595,0,20,30.783515298592842,1.9728749999785578,2100.3851669999563,3863.286711829078
|
||||||
|
1780656899,50,,Aggregated,49.200000,0.000000,20,23,26,28,36,52,150,320,2000,2100,2100,9642,0,20,30.732678433416144,1.9728749999785578,2100.3851669999563,3862.0110972827215
|
||||||
|
1780656900,50,,Aggregated,50.400000,0.000000,20,23,26,28,36,52,150,320,2000,2100,2100,9705,0,20,30.661870848119293,1.9728749999785578,2100.3851669999563,3862.7915507470375
|
||||||
|
1780656901,50,,Aggregated,49.600000,0.000000,20,23,26,28,36,52,150,320,2000,2100,2100,9743,0,20,30.613618012008416,1.9728749999785578,2100.3851669999563,3863.125936569845
|
||||||
|
1780656902,50,,Aggregated,49.600000,0.000000,20,23,26,28,36,52,150,320,2000,2100,2100,9797,0,20,30.5361544904561,1.9728749999785578,2100.3851669999563,3862.149433500051
|
||||||
|
1780656903,50,,Aggregated,50.600000,0.000000,20,23,26,28,36,51,140,320,2000,2100,2100,9856,0,20,30.462772913555053,1.9728749999785578,2100.3851669999563,3861.0668628246754
|
||||||
|
1780656904,50,,Aggregated,49.600000,0.000000,20,23,26,28,36,51,140,320,2000,2100,2100,9900,0,20,30.415828598484783,1.9728749999785578,2100.3851669999563,3861.421414141414
|
||||||
|
1780656905,50,,Aggregated,49.500000,0.000000,20,23,26,28,36,51,140,320,2000,2100,2100,9957,0,20,30.34230205704519,1.9728749999785578,2100.3851669999563,3861.550266144421
|
||||||
|
1780656906,50,,Aggregated,51.500000,0.000000,20,23,26,28,36,51,140,320,2000,2100,2100,10013,0,20,30.27103913642258,1.9728749999785578,2100.3851669999563,3862.609507640068
|
||||||
|
1780656907,50,,Aggregated,49.700000,0.000000,20,23,26,28,36,51,140,320,2000,2100,2100,10060,0,20,30.21027058429419,1.9728749999785578,2100.3851669999563,3862.4100397614316
|
||||||
|
1780656908,50,,Aggregated,51.500000,0.000000,20,23,26,28,36,51,130,320,2000,2100,2100,10116,0,20,30.166062709371285,1.9728749999785578,2100.3851669999563,3862.8865164096483
|
||||||
|
1780656909,50,,Aggregated,51.700000,0.000000,20,23,26,28,36,51,130,320,2000,2100,2100,10163,0,20,30.114509121519227,1.9728749999785578,2100.3851669999563,3863.352258191479
|
||||||
|
1780656910,50,,Aggregated,51.000000,0.000000,20,23,26,28,36,51,130,310,2000,2100,2100,10209,0,20,30.076509500734616,1.9728749999785578,2100.3851669999563,3863.5442256832207
|
||||||
|
1780656911,50,,Aggregated,51.100000,0.000000,20,23,26,28,36,51,130,310,2000,2100,2100,10265,0,20,30.034741864783275,1.9728749999785578,2100.3851669999563,3862.948757915246
|
||||||
|
1780656912,50,,Aggregated,51.900000,0.000000,20,23,26,28,36,51,130,310,2000,2100,2100,10312,0,20,29.986357600271564,1.9728749999785578,2100.3851669999563,3864.402637703646
|
||||||
|
1780656913,50,,Aggregated,51.300000,0.000000,20,23,26,28,36,51,130,310,2000,2100,2100,10365,0,20,29.956643711625674,1.9728749999785578,2100.3851669999563,3864.849107573565
|
||||||
|
1780656914,50,,Aggregated,51.300000,0.000000,20,23,26,28,36,51,130,310,2000,2100,2100,10415,0,20,29.899173922803666,1.9728749999785578,2100.3851669999563,3864.2201632261163
|
||||||
|
1780656915,50,,Aggregated,51.600000,0.000000,20,23,26,28,36,50,130,310,2000,2100,2100,10463,0,20,29.85566851677342,1.9728749999785578,2100.3851669999563,3863.7941317021887
|
||||||
|
1780656916,50,,Aggregated,50.400000,0.000000,20,23,26,28,36,50,130,310,2000,2100,2100,10500,0,20,29.822678752761977,1.9728749999785578,2100.3851669999563,3863.896761904762
|
||||||
|
1780656917,50,,Aggregated,51.300000,0.000000,20,23,26,28,36,50,130,310,2000,2100,2100,10571,0,20,29.746034349635917,1.9728749999785578,2100.3851669999563,3862.40516507426
|
||||||
|
1780656918,50,,Aggregated,49.700000,0.000000,20,23,26,28,36,50,130,310,2000,2100,2100,10618,0,20,29.714128130250657,1.9728749999785578,2100.3851669999563,3862.2936522885666
|
||||||
|
1780656919,50,,Aggregated,49.500000,0.000000,20,23,26,28,36,50,120,310,2000,2100,2100,10665,0,20,29.65590691420543,1.9728749999785578,2100.3851669999563,3861.1640881387716
|
||||||
|
1780656920,50,,Aggregated,50.000000,0.000000,20,23,26,28,36,50,120,310,2000,2100,2100,10716,0,20,29.606328635031836,1.9728749999785578,2100.3851669999563,3861.2301231802912
|
||||||
|
1780656921,50,,Aggregated,50.100000,0.000000,20,23,26,28,36,50,120,310,2000,2100,2100,10761,0,20,29.58514857541132,1.9728749999785578,2100.3851669999563,3862.231948703652
|
||||||
|
1780656922,50,,Aggregated,49.800000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,10817,0,20,29.551438001571764,1.9728749999785578,2100.3851669999563,3861.673846722751
|
||||||
|
1780656923,50,,Aggregated,49.100000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,10870,0,20,29.50050425354208,1.9728749999785578,2100.3851669999563,3859.6597976080957
|
||||||
|
1780656924,50,,Aggregated,49.900000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,10919,0,20,29.509913242330114,1.9728749999785578,2100.3851669999563,3860.8591446103123
|
||||||
|
1780656925,50,,Aggregated,50.100000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,10974,0,20,29.455721381538414,1.9728749999785578,2100.3851669999563,3860.231456169127
|
||||||
|
1780656926,50,,Aggregated,50.600000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11019,0,20,29.422147996551633,1.9728749999785578,2100.3851669999563,3858.756965241855
|
||||||
|
1780656927,50,,Aggregated,49.600000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11061,0,20,29.383964588825815,1.9728749999785578,2100.3851669999563,3860.2332519663682
|
||||||
|
1780656928,50,,Aggregated,50.500000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11116,0,20,29.333866838341372,1.9728749999785578,2100.3851669999563,3859.580154731918
|
||||||
|
1780656929,50,,Aggregated,50.100000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11154,0,20,29.375816043482402,1.9728749999785578,2100.3851669999563,3860.8396987627757
|
||||||
|
1780656930,50,,Aggregated,49.800000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11210,0,20,29.32450674460331,1.9728749999785578,2100.3851669999563,3860.8382694023194
|
||||||
|
1780656931,50,,Aggregated,49.600000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11260,0,20,29.28865734440521,1.9728749999785578,2100.3851669999563,3859.782593250444
|
||||||
|
1780656932,50,,Aggregated,48.600000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11311,0,20,29.22581409601295,1.9728749999785578,2100.3851669999563,3857.476350455309
|
||||||
|
1780656933,50,,Aggregated,49.900000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11363,0,20,29.191043363460544,1.9728749999785578,2100.3851669999563,3859.639707823638
|
||||||
|
1780656934,50,,Aggregated,50.000000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11407,0,20,29.16042467256967,1.9728749999785578,2100.3851669999563,3861.4269308319454
|
||||||
|
1780656935,50,,Aggregated,48.900000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11455,0,20,29.11551476708881,1.9728749999785578,2100.3851669999563,3860.9963334788304
|
||||||
|
1780656936,50,,Aggregated,48.500000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11510,0,20,29.071895152041865,1.9728749999785578,2100.3851669999563,3859.845525629887
|
||||||
|
1780656937,50,,Aggregated,48.600000,0.000000,20,24,26,28,36,50,120,300,2000,2100,2100,11565,0,20,29.036354027324006,1.9728749999785578,2100.3851669999563,3860.1416342412454
|
||||||
|
1780656938,50,,Aggregated,48.400000,0.000000,20,24,26,28,36,50,120,290,2000,2100,2100,11611,0,20,28.98980603117747,1.9728749999785578,2100.3851669999563,3860.3232279734734
|
||||||
|
1780656939,50,,Aggregated,49.300000,0.000000,20,24,26,28,36,50,120,290,2000,2100,2100,11665,0,20,28.961330325418043,1.9728749999785578,2100.3851669999563,3861.362366052293
|
||||||
|
1780656940,50,,Aggregated,49.400000,0.000000,20,24,26,28,36,50,120,290,2000,2100,2100,11725,0,20,28.909946360597164,1.9728749999785578,2100.3851669999563,3860.5358635394455
|
||||||
|
1780656941,50,,Aggregated,49.600000,0.000000,20,24,26,28,36,50,120,290,2000,2100,2100,11780,0,20,28.85182485764023,1.9728749999785578,2100.3851669999563,3858.491001697793
|
||||||
|
1780656942,50,,Aggregated,50.900000,0.000000,20,24,26,28,36,49,120,290,2000,2100,2100,11827,0,20,28.813795290775474,1.9728749999785578,2100.3851669999563,3859.3426904540456
|
||||||
|
1780656943,50,,Aggregated,51.300000,0.000000,20,24,26,28,36,49,120,290,2000,2100,2100,11882,0,20,28.77666876241387,1.9728749999785578,2100.3851669999563,3860.962969197105
|
||||||
|
1780656944,50,,Aggregated,51.300000,0.000000,20,24,26,28,36,49,120,290,2000,2100,2100,11926,0,20,28.73856039552252,1.9728749999785578,2100.3851669999563,3860.4144725809156
|
||||||
|
1780656945,50,,Aggregated,51.200000,0.000000,20,24,26,28,36,49,110,290,2000,2100,2100,11980,0,20,28.700996228213842,1.9728749999785578,2100.3851669999563,3860.946994991653
|
||||||
|
1780656946,50,,Aggregated,52.000000,0.000000,20,24,26,28,36,49,110,290,1900,2100,2100,12030,0,20,28.68137692061528,1.9728749999785578,2100.3851669999563,3859.9417290108063
|
||||||
|
1780656947,50,,Aggregated,51.500000,0.000000,20,24,26,29,36,49,110,290,1900,2100,2100,12080,0,20,28.647469227483565,1.9728749999785578,2100.3851669999563,3859.8615066225166
|
||||||
|
1780656948,50,,Aggregated,51.500000,0.000000,20,24,26,28,36,49,110,290,1900,2100,2100,12131,0,20,28.604492764570175,1.9728749999785578,2100.3851669999563,3858.99620806199
|
||||||
|
1780656949,50,,Aggregated,51.900000,0.000000,20,24,26,29,36,49,110,290,1900,2100,2100,12185,0,20,28.57330259294226,1.9728749999785578,2100.3851669999563,3859.1382847763643
|
||||||
|
1780656950,50,,Aggregated,51.800000,0.000000,20,24,26,29,36,49,110,290,1900,2100,2100,12240,0,20,28.524098598202702,1.9728749999785578,2100.3851669999563,3857.9819444444443
|
||||||
|
1780656951,50,,Aggregated,51.800000,0.000000,20,24,26,29,36,49,110,290,1900,2100,2100,12300,0,20,28.494272572032642,1.9728749999785578,2100.3851669999563,3856.4261788617887
|
||||||
|
1780656952,50,,Aggregated,51.600000,0.000000,21,24,26,29,36,49,110,280,1900,2100,2100,12346,0,21,28.46738666798981,1.9728749999785578,2100.3851669999563,3857.1087801717154
|
||||||
|
1780656953,50,,Aggregated,51.100000,0.000000,21,24,26,29,36,49,110,280,1900,2100,2100,12402,0,21,28.4440260684568,1.9728749999785578,2100.3851669999563,3858.828092243187
|
||||||
|
1780656954,50,,Aggregated,51.600000,0.000000,21,24,26,29,36,49,110,280,1900,2100,2100,12447,0,21,28.426802392785483,1.9728749999785578,2100.3851669999563,3859.7521491122357
|
||||||
|
1780656955,50,,Aggregated,52.000000,0.000000,21,24,27,29,36,49,110,280,1900,2100,2100,12491,0,21,28.412743456168556,1.9728749999785578,2100.3851669999563,3860.086942598671
|
||||||
|
1780656956,50,,Aggregated,51.700000,0.000000,21,24,27,29,36,49,110,280,1900,2100,2100,12539,0,21,28.387725756519732,1.9728749999785578,2100.3851669999563,3860.5980540712976
|
||||||
|
1780656957,50,,Aggregated,52.200000,0.000000,21,24,27,29,36,49,100,280,1900,2100,2100,12586,0,21,28.355236189178477,1.9728749999785578,2100.3851669999563,3859.6473859844273
|
||||||
|
1780656958,50,,Aggregated,51.100000,0.000000,21,24,27,29,36,49,100,280,1900,2100,2100,12631,0,21,28.34280927305838,1.9728749999785578,2100.3851669999563,3860.068482305439
|
||||||
|
1780656959,50,,Aggregated,50.400000,0.000000,21,24,27,29,36,49,100,280,1900,2100,2100,12687,0,21,28.31183909174746,1.9728749999785578,2100.3851669999563,3860.507212106881
|
||||||
|
1780656961,50,,Aggregated,50.100000,0.000000,21,24,27,29,36,49,100,270,1900,2100,2100,12730,0,21,28.297900551374696,1.9728749999785578,2100.3851669999563,3861.985153181461
|
||||||
|
1780656962,50,,Aggregated,49.700000,0.000000,21,24,27,29,36,49,100,270,1900,2100,2100,12778,0,21,28.266660120128282,1.9728749999785578,2100.3851669999563,3862.9128189074972
|
||||||
|
1780656963,50,,Aggregated,48.800000,0.000000,21,24,27,29,36,49,100,270,1900,2100,2100,12825,0,21,28.25806143399601,1.9728749999785578,2100.3851669999563,3864.4669785575047
|
||||||
|
1780656964,50,,Aggregated,48.800000,0.000000,21,24,27,29,36,49,98,270,1900,2100,2100,12864,0,21,28.229351936100667,1.9728749999785578,2100.3851669999563,3864.882462686567
|
||||||
|
1780656965,50,,Aggregated,47.600000,0.000000,21,24,27,29,36,49,98,270,1900,2100,2100,12919,0,21,28.19653644941554,1.9728749999785578,2100.3851669999563,3864.316742781949
|
||||||
|
1780656966,50,,Aggregated,47.300000,0.000000,21,24,27,29,36,49,98,270,1900,2100,2100,12974,0,21,28.169323862956677,1.9728749999785578,2100.3851669999563,3864.4687066440574
|
||||||
|
1780656967,50,,Aggregated,47.000000,0.000000,21,24,27,29,36,49,97,270,1900,2100,2100,13021,0,21,28.142310553413658,1.9728749999785578,2100.3851669999563,3864.764995008064
|
||||||
|
1780656968,50,,Aggregated,48.300000,0.000000,21,24,27,29,36,49,97,270,1900,2100,2100,13080,0,21,28.107046990137558,1.9728749999785578,2100.3851669999563,3864.753516819572
|
||||||
|
1780656969,50,,Aggregated,48.300000,0.000000,21,24,27,29,36,49,96,260,1900,2100,2100,13123,0,21,28.080386599329362,1.9728749999785578,2100.3851669999563,3865.6895526937437
|
||||||
|
1780656970,50,,Aggregated,49.100000,0.000000,21,24,27,29,36,49,95,260,1900,2100,2100,13170,0,21,28.044080673576254,1.9728749999785578,2100.3851669999563,3864.3735763097948
|
||||||
|
1780656971,50,,Aggregated,49.100000,0.000000,21,24,27,29,36,49,95,260,1900,2100,2100,13216,0,21,28.022711313861933,1.9728749999785578,2100.3851669999563,3865.7754993946733
|
||||||
|
1780656972,50,,Aggregated,48.200000,0.000000,21,24,27,29,36,48,95,260,1900,2100,2100,13277,0,21,27.98161367861714,1.9728749999785578,2100.3851669999563,3865.187467048279
|
||||||
|
1780656973,50,,Aggregated,48.500000,0.000000,21,24,27,29,36,48,95,260,1900,2100,2100,13327,0,21,27.9449912755309,1.9728749999785578,2100.3851669999563,3865.0495235236735
|
||||||
|
1780656974,50,,Aggregated,49.500000,0.000000,21,24,27,29,36,49,94,260,1900,2100,2100,13372,0,21,27.94887474910262,1.9728749999785578,2100.3851669999563,3865.091310200419
|
||||||
|
1780656975,50,,Aggregated,49.500000,0.000000,21,24,27,29,36,49,94,260,1900,2100,2100,13415,0,21,27.92022844934776,1.9728749999785578,2100.3851669999563,3864.4004472605293
|
||||||
|
1780656976,50,,Aggregated,50.700000,0.000000,21,24,27,29,36,49,94,260,1900,2100,2100,13463,0,21,27.908495770704878,1.9728749999785578,2100.3851669999563,3864.8464680977495
|
||||||
|
1780656977,50,,Aggregated,49.600000,0.000000,21,24,27,29,36,49,93,250,1900,2100,2100,13511,0,21,27.886016062319584,1.9728749999785578,2100.3851669999563,3865.6989119976315
|
||||||
|
1780656978,50,,Aggregated,48.700000,0.000000,21,24,27,29,36,49,93,250,1900,2100,2100,13560,0,21,27.86637965671089,1.9728749999785578,2100.3851669999563,3866.688274336283
|
||||||
|
1780656979,50,,Aggregated,48.900000,0.000000,21,24,27,29,36,49,93,250,1900,2100,2100,13618,0,21,27.845275822147123,1.9728749999785578,2100.3851669999563,3866.4676898222942
|
||||||
|
1780656980,50,,Aggregated,47.600000,0.000000,21,24,27,29,36,49,93,250,1900,2100,2100,13669,0,21,27.82439093788863,1.9728749999785578,2100.3851669999563,3865.7345087424096
|
||||||
|
1780656981,50,,Aggregated,48.900000,0.000000,21,24,27,29,36,49,93,250,1900,2100,2100,13714,0,21,27.798143489426817,1.9728749999785578,2100.3851669999563,3866.1025958874143
|
||||||
|
1780656982,50,,Aggregated,49.600000,0.000000,21,24,27,29,36,49,93,250,1900,2100,2100,13761,0,21,27.783504855243,1.9728749999785578,2100.3851669999563,3867.2408981905387
|
||||||
|
1780656983,50,,Aggregated,49.600000,0.000000,21,24,27,29,37,49,92,250,1900,2100,2100,13818,0,21,27.77878524851634,1.9728749999785578,2100.3851669999563,3868.5273556231004
|
||||||
|
1780656984,50,,Aggregated,48.300000,0.000000,21,24,27,29,37,49,92,250,1900,2100,2100,13859,0,21,27.758240335521926,1.9728749999785578,2100.3851669999563,3868.781585973014
|
||||||
|
1780656985,50,,Aggregated,48.500000,0.000000,21,24,27,29,37,49,91,250,1900,2100,2100,13905,0,21,27.74126741093118,1.9728749999785578,2100.3851669999563,3867.798417835311
|
||||||
|
1780656986,50,,Aggregated,48.300000,0.000000,21,24,27,29,37,49,91,250,1900,2100,2100,13954,0,21,27.725403057331082,1.9728749999785578,2100.3851669999563,3866.7979074100617
|
||||||
|
1780656987,50,,Aggregated,48.000000,0.000000,21,24,27,29,37,49,91,240,1900,2100,2100,14009,0,21,27.684374989221066,1.9728749999785578,2100.3851669999563,3865.4786922692556
|
||||||
|
1780656988,50,,Aggregated,48.400000,0.000000,21,24,27,29,37,49,91,240,1900,2100,2100,14053,0,21,27.670049762043554,1.9728749999785578,2100.3851669999563,3866.094499395147
|
||||||
|
1780656989,50,,Aggregated,49.800000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14111,0,21,27.650994355396342,1.9728749999785578,2100.3851669999563,3865.926723832471
|
||||||
|
1780656990,50,,Aggregated,49.000000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14145,0,21,27.639001429833726,1.9728749999785578,2100.3851669999563,3866.7893248497703
|
||||||
|
1780656991,50,,Aggregated,48.900000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14194,0,21,27.606072191418736,1.9728749999785578,2100.3851669999563,3866.575243060448
|
||||||
|
1780656992,50,,Aggregated,47.700000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14253,0,21,27.602201686662333,1.9728749999785578,2100.3851669999563,3866.2116045744756
|
||||||
|
1780656993,50,,Aggregated,47.800000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14300,0,21,27.586359837692214,1.9728749999785578,2100.3851669999563,3866.48965034965
|
||||||
|
1780656994,50,,Aggregated,48.300000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14352,0,21,27.552024762123665,1.9728749999785578,2100.3851669999563,3866.298285953177
|
||||||
|
1780656995,50,,Aggregated,48.700000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14399,0,21,27.543531979373473,1.9728749999785578,2100.3851669999563,3866.5602472393916
|
||||||
|
1780656996,50,,Aggregated,49.000000,0.000000,21,24,27,29,37,49,90,240,1900,2100,2100,14447,0,21,27.534738126946653,1.9728749999785578,2100.3851669999563,3868.4736623520453
|
||||||
|
1780656997,50,,Aggregated,48.900000,0.000000,21,24,27,29,37,49,89,240,1900,2100,2100,14493,0,21,27.518151080176512,1.9728749999785578,2100.3851669999563,3869.6693576209204
|
||||||
|
1780656998,50,,Aggregated,49.100000,0.000000,21,24,27,29,37,49,89,240,1900,2100,2100,14543,0,21,27.498520291824097,1.9728749999785578,2100.3851669999563,3870.6839716702193
|
||||||
|
1780656999,50,,Aggregated,48.000000,0.000000,21,24,27,29,37,49,89,240,1900,2100,2100,14598,0,21,27.481470106315818,1.9728749999785578,2100.3851669999563,3869.7130428825867
|
||||||
|
1780657000,50,,Aggregated,48.800000,0.000000,21,24,27,29,37,49,89,230,1900,2100,2100,14642,0,21,27.46531830412502,1.9728749999785578,2100.3851669999563,3870.7113099303374
|
||||||
|
1780657001,50,,Aggregated,47.500000,0.000000,21,24,27,29,37,49,89,230,1900,2100,2100,14692,0,21,27.44931532609574,1.9728749999785578,2100.3851669999563,3871.3490334876124
|
||||||
|
1780657002,50,,Aggregated,48.800000,0.000000,21,24,27,29,37,49,89,230,1900,2100,2100,14739,0,21,27.430747146074935,1.9728749999785578,2100.3851669999563,3870.8652554447385
|
||||||
|
1780657003,50,,Aggregated,48.900000,0.000000,21,24,27,29,37,49,89,230,1900,2100,2100,14791,0,21,27.409774047528764,1.9728749999785578,2100.3851669999563,3871.0110878236765
|
||||||
|
1780657004,50,,Aggregated,48.600000,0.000000,21,24,27,29,37,49,89,230,1900,2100,2100,14846,0,21,27.37380920685692,1.9728749999785578,2100.3851669999563,3869.709686110737
|
||||||
|
1780657005,50,,Aggregated,48.600000,0.000000,21,24,27,29,37,49,88,230,1900,2100,2100,14894,0,21,27.340546631193618,1.9728749999785578,2100.3851669999563,3868.622599704579
|
||||||
|
1780657006,50,,Aggregated,49.300000,0.000000,21,24,27,29,37,49,88,230,1900,2100,2100,14944,0,21,27.332187992170656,1.9728749999785578,2100.3851669999563,3869.970289079229
|
||||||
|
1780657007,50,,Aggregated,50.000000,0.000000,21,25,27,29,37,49,88,230,1900,2100,2100,14993,0,21,27.32649379323675,1.9728749999785578,2100.3851669999563,3870.0993797105316
|
||||||
|
1780657008,50,,Aggregated,49.400000,0.000000,21,25,27,29,37,49,87,230,1900,2100,2100,15041,0,21,27.33634289508665,1.9728749999785578,2100.3851669999563,3871.5310152250513
|
||||||
|
1780657009,50,,Aggregated,49.700000,0.000000,21,25,27,29,37,49,87,230,1900,2100,2100,15099,0,21,27.31666940857003,1.9728749999785578,2100.3851669999563,3871.66679912577
|
||||||
|
1780657010,50,,Aggregated,50.000000,0.000000,21,25,27,29,37,49,87,220,1900,2100,2100,15140,0,21,27.312017455548144,1.9728749999785578,2100.3851669999563,3872.5822324966975
|
||||||
|
1780657011,50,,Aggregated,50.700000,0.000000,21,25,27,29,37,49,87,220,1900,2100,2100,15192,0,21,27.30668507760654,1.9728749999785578,2100.3851669999563,3873.7718536071616
|
||||||
|
1780657012,50,,Aggregated,49.900000,0.000000,21,25,27,29,37,49,87,220,1900,2100,2100,15242,0,21,27.273282213226537,1.9728749999785578,2100.3851669999563,3872.613764597822
|
||||||
|
1780657013,50,,Aggregated,50.500000,0.000000,21,25,27,29,37,49,87,220,1900,2100,2100,15291,0,21,27.24925591818713,1.9728749999785578,2100.3851669999563,3872.4094565430646
|
||||||
|
1780657014,50,,Aggregated,50.200000,0.000000,21,25,27,29,37,49,87,220,1900,2100,2100,15338,0,21,27.219907256226332,1.9728749999785578,2100.3851669999563,3870.8850567218674
|
||||||
|
1780657015,50,,Aggregated,49.400000,0.000000,21,25,27,29,37,49,87,220,1900,2100,2100,15399,0,21,27.222882573413855,1.9728749999785578,2100.3851669999563,3871.46334177544
|
||||||
|
1780657016,50,,Aggregated,49.300000,0.000000,21,25,27,29,37,49,87,210,1900,2100,2100,15440,0,21,27.209672047409324,1.9728749999785578,2100.3851669999563,3870.9798575129535
|
||||||
|
1780657017,50,,Aggregated,49.000000,0.000000,21,25,27,29,37,49,86,210,1900,2100,2100,15490,0,21,27.193581872304737,1.9728749999785578,2100.3851669999563,3872.2727566171725
|
||||||
|
1780657018,50,,Aggregated,49.100000,0.000000,21,25,27,29,37,49,86,210,1900,2100,2100,15542,0,21,27.18145434615886,1.9728749999785578,2100.3851669999563,3871.707566593746
|
||||||
|
1780657019,50,,Aggregated,49.200000,0.000000,21,25,27,29,37,49,86,210,1900,2100,2100,15583,0,21,27.161467923891493,1.9728749999785578,2100.3851669999563,3871.557402297375
|
||||||
|
1780657020,50,,Aggregated,49.100000,0.000000,21,25,27,30,37,49,86,210,1900,2100,2100,15631,0,21,27.1566626410339,1.9728749999785578,2100.3851669999563,3872.6151237924637
|
||||||
|
1780657021,50,,Aggregated,48.900000,0.000000,21,25,27,30,37,49,86,210,1900,2100,2100,15683,0,21,27.138641264362732,1.9728749999785578,2100.3851669999563,3873.427277944271
|
||||||
|
1780657022,50,,Aggregated,48.600000,0.000000,21,25,27,30,37,49,86,210,1900,2100,2100,15732,0,21,27.133053875413232,1.9728749999785578,2100.3851669999563,3873.2135774218154
|
||||||
|
1780657023,50,,Aggregated,48.300000,0.000000,21,25,27,30,37,49,86,210,1900,2100,2100,15789,0,21,27.11067760440822,1.9728749999785578,2100.3851669999563,3872.954081955792
|
||||||
|
1780657024,50,,Aggregated,49.300000,0.000000,21,25,27,30,37,49,86,210,1900,2100,2100,15834,0,21,27.098111133952358,1.9728749999785578,2100.3851669999563,3872.9415182518633
|
||||||
|
1780657025,50,,Aggregated,49.400000,0.000000,21,25,27,30,37,49,86,210,1900,2100,2100,15883,0,21,27.07332276030989,1.9728749999785578,2100.3851669999563,3871.72379273437
|
||||||
|
1780657026,50,,Aggregated,48.600000,0.000000,21,25,27,30,37,49,86,200,1900,2100,2100,15931,0,21,27.05605365727211,1.9728749999785578,2100.3851669999563,3871.3585462306196
|
||||||
|
1780657027,50,,Aggregated,49.400000,0.000000,21,25,27,30,37,49,85,200,1900,2100,2100,15984,0,21,27.031048974912522,1.9728749999785578,2100.3851669999563,3870.611986986987
|
||||||
|
1780657028,50,,Aggregated,49.300000,0.000000,21,25,27,30,37,49,85,200,1900,2100,2100,16030,0,21,27.020205940673833,1.9728749999785578,2100.3851669999563,3870.701497192764
|
||||||
|
1780657029,50,,Aggregated,49.200000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16087,0,21,27.007237866662635,1.9728749999785578,2100.3851669999563,3868.747435817741
|
||||||
|
1780657030,50,,Aggregated,49.000000,0.000000,21,25,28,30,37,49,86,200,1900,2100,2100,16130,0,21,27.03126743006829,1.9728749999785578,2100.3851669999563,3870.2102293862367
|
||||||
|
1780657031,50,,Aggregated,49.800000,0.000000,21,25,28,30,37,49,86,200,1900,2100,2100,16170,0,21,27.032959424737232,1.9728749999785578,2100.3851669999563,3872.2963512677798
|
||||||
|
1780657032,50,,Aggregated,49.200000,0.000000,21,25,28,30,37,49,86,200,1900,2100,2100,16226,0,21,27.007158925613272,1.9728749999785578,2100.3851669999563,3871.915382719093
|
||||||
|
1780657033,50,,Aggregated,49.000000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16279,0,21,26.99575899803435,1.9728749999785578,2100.3851669999563,3871.5085078936054
|
||||||
|
1780657034,50,,Aggregated,48.500000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16318,0,21,26.983226466601373,1.9728749999785578,2100.3851669999563,3871.455509253585
|
||||||
|
1780657035,50,,Aggregated,49.300000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16371,0,21,26.957341573575288,1.9728749999785578,2100.3851669999563,3870.7634842098832
|
||||||
|
1780657036,50,,Aggregated,49.000000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16421,0,21,26.947858861031666,1.9728749999785578,2100.3851669999563,3870.659764935144
|
||||||
|
1780657037,50,,Aggregated,48.400000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16477,0,21,26.94394531419559,1.9728749999785578,2100.3851669999563,3871.5604175517387
|
||||||
|
1780657038,50,,Aggregated,49.000000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16523,0,21,26.926001060461218,1.9728749999785578,2100.3851669999563,3871.0114386007385
|
||||||
|
1780657039,50,,Aggregated,48.700000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16583,0,21,26.90251755213177,1.9728749999785578,2100.3851669999563,3870.4296568775253
|
||||||
|
1780657040,50,,Aggregated,48.700000,0.000000,21,25,28,30,37,49,85,200,1900,2100,2100,16628,0,21,26.889299995128734,1.9728749999785578,2100.3851669999563,3869.7463916285783
|
||||||
|
1780657041,50,,Aggregated,49.000000,0.000000,21,25,28,30,37,49,84,200,1900,2100,2100,16682,0,21,26.88886371700039,1.9728749999785578,2100.3851669999563,3870.738940175039
|
||||||
|
1780657042,50,,Aggregated,50.600000,0.000000,21,25,28,30,38,49,84,200,1900,2100,2100,16733,0,21,26.88446525966655,1.9728749999785578,2100.3851669999563,3870.470686667065
|
||||||
|
1780657043,50,,Aggregated,50.800000,0.000000,21,25,28,30,38,49,84,200,1900,2100,2100,16775,0,21,26.87713211552906,1.9728749999785578,2100.3851669999563,3869.8014903129656
|
||||||
|
1780657044,50,,Aggregated,50.300000,0.000000,21,25,28,30,38,49,84,200,1900,2100,2100,16831,0,21,26.868114997682856,1.9728749999785578,2100.3851669999563,3869.4307527776127
|
||||||
|
1780657045,50,,Aggregated,49.000000,0.000000,21,25,28,30,38,49,84,200,1900,2100,2100,16885,0,21,26.870218598104806,1.9728749999785578,2100.3851669999563,3869.4648504589873
|
||||||
|
1780657046,50,,Aggregated,50.400000,0.000000,21,25,28,30,38,49,84,190,1900,2100,2100,16938,0,21,26.84933163761951,1.9728749999785578,2100.3851669999563,3867.506730428622
|
||||||
|
1780657047,50,,Aggregated,50.100000,0.000000,21,25,28,30,38,49,84,190,1900,2100,2100,16978,0,21,26.84047974985272,1.9728749999785578,2100.3851669999563,3868.1834138296617
|
||||||
|
1780657048,50,,Aggregated,50.200000,0.000000,21,25,28,30,38,49,84,190,1900,2100,2100,17023,0,21,26.84894357063972,1.9728749999785578,2100.3851669999563,3868.8350467015216
|
||||||
|
1780657049,50,,Aggregated,50.500000,0.000000,21,25,28,30,38,50,84,190,1900,2100,2100,17078,0,21,26.83914664246394,1.9728749999785578,2100.3851669999563,3869.632451106687
|
||||||
|
1780657050,50,,Aggregated,51.100000,0.000000,21,25,28,30,38,50,83,190,1900,2100,2100,17133,0,21,26.815694419366103,1.9728749999785578,2100.3851669999563,3867.875036479309
|
||||||
|
1780657051,50,,Aggregated,49.300000,0.000000,21,25,28,30,38,49,83,190,1900,2100,2100,17180,0,21,26.80472681356225,1.9728749999785578,2100.3851669999563,3868.413038416764
|
||||||
|
1780657052,50,,Aggregated,49.000000,0.000000,21,25,28,30,38,49,83,180,1900,2100,2100,17234,0,21,26.79177492584421,1.9728749999785578,2100.3851669999563,3868.473076476732
|
||||||
|
1780657053,50,,Aggregated,49.500000,0.000000,21,25,28,30,38,49,83,180,1900,2100,2100,17282,0,21,26.775228134012256,1.9728749999785578,2100.3851669999563,3868.512845735447
|
||||||
|
1780657054,50,,Aggregated,50.000000,0.000000,21,25,28,30,38,49,82,170,1900,2100,2100,17317,0,21,26.769471190968382,1.9728749999785578,2100.3851669999563,3869.5740024253623
|
||||||
|
1780657055,50,,Aggregated,50.300000,0.000000,21,25,28,30,38,49,82,170,1900,2100,2100,17382,0,21,26.764110957082,1.9728749999785578,2100.3851669999563,3868.648717063629
|
||||||
|
1780657056,50,,Aggregated,49.200000,0.000000,21,25,28,30,38,49,82,170,1900,2100,2100,17427,0,21,26.750794176622435,1.9728749999785578,2100.3851669999563,3868.0250186492226
|
||||||
|
1780657057,50,,Aggregated,49.900000,0.000000,21,25,28,30,38,49,82,170,1900,2100,2100,17479,0,21,26.72365143824012,1.9728749999785578,2100.3851669999563,3866.608444419017
|
||||||
|
1780657058,50,,Aggregated,49.700000,0.000000,21,25,28,30,38,49,83,170,1900,2100,2100,17536,0,21,26.73984157065458,1.9728749999785578,2100.3851669999563,3867.882698448905
|
||||||
|
1780657059,50,,Aggregated,49.700000,0.000000,21,25,28,30,38,49,83,170,1900,2100,2100,17569,0,21,26.737537214639357,1.9728749999785578,2100.3851669999563,3868.1135522795835
|
||||||
|
1780657060,50,,Aggregated,49.100000,0.000000,21,25,28,30,38,49,82,170,1900,2100,2100,17623,0,21,26.722573142427443,1.9728749999785578,2100.3851669999563,3867.5909323043747
|
||||||
|
1780657061,50,,Aggregated,49.300000,0.000000,21,25,28,30,38,49,82,170,1900,2100,2100,17671,0,21,26.719459754343205,1.9728749999785578,2100.3851669999563,3869.4777884669797
|
||||||
|
1780657062,50,,Aggregated,49.700000,0.000000,22,25,28,30,38,49,82,170,1900,2100,2100,17717,0,22,26.703707624597772,1.9728749999785578,2100.3851669999563,3868.3463340294634
|
||||||
|
1780657063,50,,Aggregated,48.400000,0.000000,22,25,28,30,38,50,83,170,1900,2100,2100,17770,0,22,26.7189652073719,1.9728749999785578,2100.3851669999563,3867.672763083849
|
||||||
|
1780657064,50,,Aggregated,48.400000,0.000000,22,25,28,30,38,50,83,170,1900,2100,2100,17810,0,22,26.707179846546783,1.9728749999785578,2100.3851669999563,3867.1768669286917
|
||||||
|
1780657065,50,,Aggregated,48.900000,0.000000,22,25,28,30,38,50,82,170,1900,2100,2100,17863,0,22,26.6968239534232,1.9728749999785578,2100.3851669999563,3866.842915523708
|
||||||
|
1780657066,50,,Aggregated,48.900000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,17913,0,22,26.6931026761011,1.9728749999785578,2100.3851669999563,3866.450064199185
|
||||||
|
1780657067,50,,Aggregated,48.300000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,17956,0,22,26.689276666406702,1.9728749999785578,2100.3851669999563,3866.822287814658
|
||||||
|
1780657068,50,,Aggregated,48.600000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,18018,0,22,26.666236903596324,1.9728749999785578,2100.3851669999563,3865.013764013764
|
||||||
|
1780657069,50,,Aggregated,48.100000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,18066,0,22,26.680822091387054,1.9728749999785578,2100.3851669999563,3865.3337208015055
|
||||||
|
1780657070,50,,Aggregated,48.400000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,18114,0,22,26.671547554488182,1.9728749999785578,2100.3851669999563,3864.4520812631113
|
||||||
|
1780657071,50,,Aggregated,48.400000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,18165,0,22,26.668111553151615,1.9728749999785578,2100.3851669999563,3865.397357555739
|
||||||
|
1780657072,50,,Aggregated,49.200000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,18213,0,22,26.652271451490645,1.9728749999785578,2100.3851669999563,3865.128205128205
|
||||||
|
1780657073,50,,Aggregated,48.400000,0.000000,22,25,28,30,38,50,82,160,1900,2100,2100,18263,0,22,26.641601974812403,1.9728749999785578,2100.3851669999563,3865.6569566883863
|
||||||
|
1780657074,50,,Aggregated,49.800000,0.000000,22,26,28,31,38,50,82,160,1900,2100,2100,18318,0,22,26.63283019614582,1.9728749999785578,2100.3851669999563,3865.2342504640246
|
||||||
|
1780657075,50,,Aggregated,49.300000,0.000000,22,26,28,31,38,50,82,160,1900,2100,2100,18365,0,22,26.62033654119245,1.9728749999785578,2100.3851669999563,3864.8732371358565
|
||||||
|
1780657076,50,,Aggregated,49.000000,0.000000,22,26,28,31,38,50,81,160,1900,2100,2100,18411,0,22,26.611302769159668,1.9728749999785578,2100.3851669999563,3865.299657813264
|
||||||
|
1780657077,50,,Aggregated,50.000000,0.000000,22,26,28,31,38,50,81,160,1900,2100,2100,18468,0,22,26.599839212529705,1.9728749999785578,2100.3851669999563,3864.503140567468
|
||||||
|
1780657078,50,,Aggregated,49.300000,0.000000,22,26,28,31,38,50,81,160,1900,2100,2100,18525,0,22,26.597556664075512,1.9728749999785578,2100.3851669999563,3865.172955465587
|
||||||
|
1780657079,50,,Aggregated,49.600000,0.000000,22,26,28,31,38,50,81,160,1900,2100,2100,18575,0,22,26.588195320538304,1.9728749999785578,2100.3851669999563,3864.842261103634
|
||||||
|
1780657080,50,,Aggregated,49.600000,0.000000,22,26,28,31,38,50,82,150,1900,2100,2100,18630,0,22,26.612855197530767,1.9728749999785578,2100.3851669999563,3863.8455179817497
|
||||||
|
1780657081,50,,Aggregated,51.000000,0.000000,22,26,28,31,38,50,82,150,1900,2100,2100,18678,0,22,26.605391971731322,1.9728749999785578,2100.3851669999563,3863.598029767641
|
||||||
|
1780657082,50,,Aggregated,49.100000,0.000000,22,26,28,31,38,50,82,150,1900,2100,2100,18722,0,22,26.614379186037706,1.9728749999785578,2100.3851669999563,3864.645977993804
|
||||||
|
1780657083,50,,Aggregated,51.500000,0.000000,22,26,28,31,38,50,82,150,1900,2100,2100,18779,0,22,26.600978512913247,1.9728749999785578,2100.3851669999563,3865.033228606422
|
||||||
|
Reference in New Issue
Block a user