gmal-scope-builder/backend/app/models/feedback.py
DJP 82046c784c P1: Role-specific efficiency profiles + BTG tool efficiencies
Backend:
- New models: MatchFeedback, EfficiencyProfile, EfficiencyRate, ToolEfficiency, ToolEfficiencyRate
- 3 preset profiles seeded: Conservative, Moderate, Aggressive with per-discipline rates
- 6 BTG tools seeded: Pencil, OMG, Creative X, Cortex, Semblance, Share of Model
- Efficiency API: CRUD for profiles and tools at /api/efficiency/
- team_shape.py: accepts profile_rates + tool_rates (per-discipline, additive, capped at 90%)
- team-shape endpoint: accepts profile_id and tool_ids query params
- Programme roles always exempt regardless of method

Example: Moderate profile + Creative X + Pencil → Account Mgmt 10%, Creative 70%, Production 65%

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:48:30 -04:00

71 lines
3.1 KiB
Python

"""Models for match feedback, efficiency profiles, and tool efficiencies."""
from datetime import datetime
from sqlalchemy import String, Text, Integer, Numeric, Boolean, DateTime, ForeignKey, JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class MatchFeedback(Base):
"""Stores confirmed/rejected match mappings for learning."""
__tablename__ = "match_feedback"
id: Mapped[int] = mapped_column(primary_key=True)
client_term: Mapped[str] = mapped_column(String(500), index=True)
client_description: Mapped[str | None] = mapped_column(Text)
gmal_asset_id: Mapped[int] = mapped_column(ForeignKey("gmal_assets.id"), nullable=False)
confirmed: Mapped[bool] = mapped_column(Boolean, default=True)
user_comment: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
gmal_asset: Mapped["GmalAsset"] = relationship()
class EfficiencyProfile(Base):
"""Named efficiency profile (Conservative, Moderate, Aggressive, Custom)."""
__tablename__ = "efficiency_profiles"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True)
is_default: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
rates: Mapped[list["EfficiencyRate"]] = relationship(back_populates="profile", cascade="all, delete-orphan")
class EfficiencyRate(Base):
"""Per-discipline efficiency rate within a profile."""
__tablename__ = "efficiency_rates"
id: Mapped[int] = mapped_column(primary_key=True)
profile_id: Mapped[int] = mapped_column(ForeignKey("efficiency_profiles.id", ondelete="CASCADE"), nullable=False)
discipline: Mapped[str] = mapped_column(String(100), nullable=False)
efficiency_pct: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False)
profile: Mapped["EfficiencyProfile"] = relationship(back_populates="rates")
class ToolEfficiency(Base):
"""A BTG tool that provides additional efficiency."""
__tablename__ = "tool_efficiencies"
id: Mapped[int] = mapped_column(primary_key=True)
tool_name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True)
tool_description: Mapped[str | None] = mapped_column(Text)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
rates: Mapped[list["ToolEfficiencyRate"]] = relationship(back_populates="tool", cascade="all, delete-orphan")
class ToolEfficiencyRate(Base):
"""Per-discipline efficiency delta for a specific tool."""
__tablename__ = "tool_efficiency_rates"
id: Mapped[int] = mapped_column(primary_key=True)
tool_id: Mapped[int] = mapped_column(ForeignKey("tool_efficiencies.id", ondelete="CASCADE"), nullable=False)
discipline: Mapped[str] = mapped_column(String(100), nullable=False)
additional_efficiency_pct: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False)
tool: Mapped["ToolEfficiency"] = relationship(back_populates="rates")