Files
claudetools/api/models/session.py
Mike Swanson 390b10b32c Complete Phase 6: MSP Work Tracking with Context Recall System
Implements production-ready MSP platform with cross-machine persistent memory for Claude.

API Implementation:
- 130 REST API endpoints across 21 entities
- JWT authentication on all endpoints
- AES-256-GCM encryption for credentials
- Automatic audit logging
- Complete OpenAPI documentation

Database:
- 43 tables in MariaDB (172.16.3.20:3306)
- 42 SQLAlchemy models with modern 2.0 syntax
- Full Alembic migration system
- 99.1% CRUD test pass rate

Context Recall System (Phase 6):
- Cross-machine persistent memory via database
- Automatic context injection via Claude Code hooks
- Automatic context saving after task completion
- 90-95% token reduction with compression utilities
- Relevance scoring with time decay
- Tag-based semantic search
- One-command setup script

Security Features:
- JWT tokens with Argon2 password hashing
- AES-256-GCM encryption for all sensitive data
- Comprehensive audit trail for credentials
- HMAC tamper detection
- Secure configuration management

Test Results:
- Phase 3: 38/38 CRUD tests passing (100%)
- Phase 4: 34/35 core API tests passing (97.1%)
- Phase 5: 62/62 extended API tests passing (100%)
- Phase 6: 10/10 compression tests passing (100%)
- Overall: 144/145 tests passing (99.3%)

Documentation:
- Comprehensive architecture guides
- Setup automation scripts
- API documentation at /api/docs
- Complete test reports
- Troubleshooting guides

Project Status: 95% Complete (Production-Ready)
Phase 7 (optional work context APIs) remains for future enhancement.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 06:00:26 -07:00

216 lines
6.6 KiB
Python

"""
Session model for work sessions with time tracking.
Tracks individual work sessions including client, project, machine used,
time tracking, and session documentation.
"""
from datetime import date, datetime
from typing import TYPE_CHECKING, Optional
from sqlalchemy import Boolean, DATE, ForeignKey, Index, Integer, Numeric, String, Text, TIMESTAMP
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, TimestampMixin, UUIDMixin
if TYPE_CHECKING:
from .client import Client
from .database_change import DatabaseChange
from .deployment import Deployment
from .infrastructure_change import InfrastructureChange
from .machine import Machine
from .operation_failure import OperationFailure
from .project import Project
from .work_item import WorkItem
class Session(Base, UUIDMixin, TimestampMixin):
"""
Session model representing work sessions with time tracking.
Tracks individual work sessions including which client, project, and machine
were involved, along with timing information, billability, and session documentation.
Enhanced with machine tracking to understand which machine was used for the work.
Attributes:
client_id: Foreign key to clients table
project_id: Foreign key to projects table
machine_id: Foreign key to machines table (which machine was used)
session_date: Date of the session
start_time: Session start timestamp
end_time: Session end timestamp
duration_minutes: Duration in minutes (auto-calculated or manual)
status: Session status (completed, in_progress, blocked, pending)
session_title: Brief title describing the session
summary: Markdown summary of the session
is_billable: Whether this session is billable
billable_hours: Billable hours if applicable
technician: Name of technician who performed the work
session_log_file: Path to markdown session log file
notes: Additional notes about the session
client: Relationship to Client model
project: Relationship to Project model
machine: Relationship to Machine model
"""
__tablename__ = "sessions"
# Foreign keys
client_id: Mapped[Optional[str]] = mapped_column(
String(36),
ForeignKey("clients.id", ondelete="SET NULL"),
doc="Foreign key to clients table"
)
project_id: Mapped[Optional[str]] = mapped_column(
String(36),
ForeignKey("projects.id", ondelete="SET NULL"),
doc="Foreign key to projects table"
)
machine_id: Mapped[Optional[str]] = mapped_column(
String(36),
ForeignKey("machines.id", ondelete="SET NULL"),
doc="Foreign key to machines table (which machine was used)"
)
# Session timing
session_date: Mapped[date] = mapped_column(
DATE,
nullable=False,
doc="Date of the session"
)
start_time: Mapped[Optional[datetime]] = mapped_column(
TIMESTAMP,
doc="Session start timestamp"
)
end_time: Mapped[Optional[datetime]] = mapped_column(
TIMESTAMP,
doc="Session end timestamp"
)
duration_minutes: Mapped[Optional[int]] = mapped_column(
Integer,
doc="Duration in minutes (auto-calculated or manual)"
)
# Status
status: Mapped[str] = mapped_column(
String(50),
default="completed",
server_default="completed",
doc="Session status: completed, in_progress, blocked, pending"
)
# Session details
session_title: Mapped[str] = mapped_column(
String(500),
nullable=False,
doc="Brief title describing the session"
)
summary: Mapped[Optional[str]] = mapped_column(
Text,
doc="Markdown summary of the session"
)
# Billability
is_billable: Mapped[bool] = mapped_column(
Boolean,
default=False,
server_default="0",
doc="Whether this session is billable"
)
billable_hours: Mapped[Optional[float]] = mapped_column(
Numeric(10, 2),
doc="Billable hours if applicable"
)
# Technician
technician: Mapped[Optional[str]] = mapped_column(
String(255),
doc="Name of technician who performed the work"
)
# Documentation
session_log_file: Mapped[Optional[str]] = mapped_column(
String(500),
doc="Path to markdown session log file"
)
# Notes
notes: Mapped[Optional[str]] = mapped_column(
Text,
doc="Additional notes about the session"
)
# Relationships
client: Mapped[Optional["Client"]] = relationship(
"Client",
back_populates="sessions",
doc="Relationship to Client model"
)
project: Mapped[Optional["Project"]] = relationship(
"Project",
back_populates="sessions",
doc="Relationship to Project model"
)
machine: Mapped[Optional["Machine"]] = relationship(
"Machine",
back_populates="sessions",
doc="Relationship to Machine model"
)
work_items: Mapped[list["WorkItem"]] = relationship(
"WorkItem",
back_populates="session",
cascade="all, delete-orphan",
doc="Relationship to WorkItem model"
)
operation_failures: Mapped[list["OperationFailure"]] = relationship(
"OperationFailure",
back_populates="session",
cascade="all, delete-orphan",
doc="Relationship to OperationFailure model"
)
deployments: Mapped[list["Deployment"]] = relationship(
"Deployment",
back_populates="session",
cascade="all, delete-orphan",
doc="Relationship to Deployment model"
)
database_changes: Mapped[list["DatabaseChange"]] = relationship(
"DatabaseChange",
back_populates="session",
cascade="all, delete-orphan",
doc="Relationship to DatabaseChange model"
)
infrastructure_changes: Mapped[list["InfrastructureChange"]] = relationship(
"InfrastructureChange",
back_populates="session",
cascade="all, delete-orphan",
doc="Relationship to InfrastructureChange model"
)
# Indexes
__table_args__ = (
Index("idx_sessions_client", "client_id"),
Index("idx_sessions_project", "project_id"),
Index("idx_sessions_date", "session_date"),
Index("idx_sessions_billable", "is_billable"),
Index("idx_sessions_machine", "machine_id"),
)
def __repr__(self) -> str:
"""String representation of the session."""
return f"<Session(title='{self.session_title}', date='{self.session_date}', status='{self.status}')>"