Add caveats row to Ratecard Summary sheet in Excel export

Inserts an "Assumptions / Caveats" row (row 2) in the Ratecard Summary
sheet so users can see each asset's AI-matched caveats without switching
to the Asset Detail tab. Uses the same amber colour scheme as the PDF report.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-31 18:00:29 +01:00
parent 8b01213be6
commit de150f3b57

View file

@ -64,10 +64,19 @@ async def export_ratecard_excel(db: AsyncSession, project: Project, efficiency_l
gmals_result = await db.execute(select(GmalAsset).where(GmalAsset.id.in_(gmal_ids)))
gmals = {g.id: g for g in gmals_result.scalars().all()}
# Load selected matches for caveat lookup
matches_result = await db.execute(
select(Match).where(
Match.client_asset_id.in_(asset_ids),
Match.is_selected == True,
)
)
caveat_by_asset = {m.client_asset_id: m.caveat_text or "" for m in matches_result.scalars().all()}
# Sheet 1: Ratecard Summary (roles x assets matrix)
ws1 = wb.active
ws1.title = "Ratecard Summary"
_build_ratecard_sheet(ws1, lines, roles, client_assets, gmals)
_build_ratecard_sheet(ws1, lines, roles, client_assets, gmals, caveat_by_asset)
# Sheet 2: Asset Detail
ws2 = wb.create_sheet("Asset Detail")
@ -86,8 +95,10 @@ async def export_ratecard_excel(db: AsyncSession, project: Project, efficiency_l
return _workbook_to_bytes(wb)
def _build_ratecard_sheet(ws, lines, roles, client_assets, gmals):
def _build_ratecard_sheet(ws, lines, roles, client_assets, gmals, caveats: dict | None = None):
"""Build the main ratecard matrix: rows=roles, cols=client assets."""
if caveats is None:
caveats = {}
# Get unique sorted client assets and roles
asset_ids_ordered = sorted(client_assets.keys())
role_ids_ordered = sorted(roles.keys(), key=lambda rid: (roles[rid].discipline, roles[rid].sort_order or 0))
@ -127,9 +138,24 @@ def _build_ratecard_sheet(ws, lines, roles, client_assets, gmals):
ws.cell(row=1, column=total_col, value="Total Hours").font = HEADER_FONT
ws.cell(row=1, column=total_col).fill = HEADER_FILL
# Caveats row (row 2)
CAVEAT_FONT = Font(italic=True, size=9, color="555555")
CAVEAT_FILL = PatternFill(start_color="FFFBF0", end_color="FFFBF0", fill_type="solid")
ws.cell(row=2, column=1, value="").fill = CAVEAT_FILL
ws.cell(row=2, column=2, value="Assumptions / Caveats").font = Font(italic=True, bold=True, size=9, color="92400E")
ws.cell(row=2, column=2).fill = CAVEAT_FILL
for col_idx, asset_id in enumerate(asset_ids_ordered, 3):
caveat = caveats.get(asset_id, "")
cell = ws.cell(row=2, column=col_idx, value=caveat)
cell.font = CAVEAT_FONT
cell.fill = CAVEAT_FILL
cell.alignment = Alignment(wrap_text=True, vertical="top")
ws.cell(row=2, column=total_col).fill = CAVEAT_FILL
ws.row_dimensions[2].height = 60
# Data rows
current_discipline = None
row_idx = 2
row_idx = 3
for role_id in role_ids_ordered:
role = roles[role_id]