Migration 0005 adds three new tables (and the matchconfidence enum):
client_assets, matches, ratecard_lines. All FKed to opportunities with
ondelete=CASCADE so re-running a stage cleanly wipes the downstream
artifacts that depended on it.
Stage 6 — Asset Normalizer:
- services/asset_normalizer.py runs Claude (full uploaded text + a hint
from the Stage 2 diagnosis) and submits a structured deliverables
list via tool_use.
- api/assets.py exposes CRUD on ClientAsset rows + POST .../normalize
to (re-)run the agent. Re-running wipes existing assets, which
cascades to matches and ratecard_lines automatically.
- Smoke-tested against the Versuni brief: 7 normalized assets seeded.
Stage 7 — AI matching:
- services/ai_matching.py is the V1 engine ported and slimmed for V2:
full GMAL catalog sent per call (~3k–20k tokens depending on whether
AI-enhanced descriptions are populated), Claude returns up to 3
candidates, top match auto-selected when score >= 0.8, alternatives
kept only when within 5% of the top score.
- BackgroundTasks runs the agent off-request so the frontend can poll
GET /matches for progress. Per-call AI cost rolls onto the opportunity.
- api/matching.py: POST /match (kick off), GET /matches, PUT
/matches/{id}/select (toggle the chosen one — auto-deselects siblings
so there's exactly one selection per asset).
- Smoke-tested: 15 matches across 7 assets, GMAL323 et al picked with
reasonable scores.
Stage 8 — Ratecard:
- services/ratecard_builder.py is the V1 builder, with the V1 hours×
volume bug-fix already baked in: total_hours stores per-1-asset
hours; volume sits on the row; aggregators multiply at read time.
- api/ratecard.py: POST /ratecard/build, GET /ratecard returns a
RatecardSummary whose total_hours is correctly computed as
sum(per_asset × volume).
- Smoke-tested: 24 lines, 5,620.5 total project hours, base × volume
displayed separately.
Tests added by the parallel test agent (commit was queued during build):
- test_qualification.py (10): TROWLS save/get, threshold boundaries
(50%/60%), Pydantic 0–10 range guards, missing-dimension 422,
qualification_score stamped on opportunity, newest-scorecard wins,
404 paths.
- test_qa_pack.py (5): Excel + Word downloads (real PK\\x03\\x04
signature, openpyxl-parseable, priority-sort verified), filename
safety against /, ?, ", 404 paths.
Suite: 73 collected, 70 passed, 3 skipped (real-Anthropic), 0 failed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 lines
935 B
Python
34 lines
935 B
Python
"""SQLAlchemy models. Importing this package registers all models on Base.metadata
|
|
so Alembic autogenerate can detect them."""
|
|
|
|
from app.models.user import AppUser, UserRole # noqa: F401
|
|
from app.models.gmal import ( # noqa: F401
|
|
GmalAsset,
|
|
Role,
|
|
GmalHours,
|
|
GmalServiceLine,
|
|
RoleLevelMapping,
|
|
ModelType,
|
|
MODEL_TYPE_MAP,
|
|
)
|
|
from app.models.opportunity import Opportunity, DealStatus # noqa: F401
|
|
from app.models.stage import ( # noqa: F401
|
|
StageState,
|
|
StageStatus,
|
|
StageArtifact,
|
|
Approval,
|
|
ApprovalStatus,
|
|
)
|
|
from app.models.file import OpportunityFile # noqa: F401
|
|
from app.models.clarification import ( # noqa: F401
|
|
ClarificationQuestion,
|
|
QuestionPriority,
|
|
QuestionStatus,
|
|
)
|
|
from app.models.notification import Notification, NotificationType # noqa: F401
|
|
from app.models.asset import ( # noqa: F401
|
|
ClientAsset,
|
|
Match,
|
|
MatchConfidence,
|
|
RatecardLine,
|
|
)
|