feat: Specialist Desks — per asset-class fundamental configs + report catalogue

- 7 pre-seeded desks (Forex, Metals, Agri, Energy, Indices, Crypto, Bonds)
  each with default fundamental drivers, macro regime sensitivities and
  price delta thresholds
- Global report catalogue (specialist_reports) fully manual — add any report
  including non-calendar ones (e.g. Cocoa Grinding Report, ICCO)
- Many-to-many report ↔ desk linking (report_desk_links table)
- 12 default reports pre-seeded (COT, EIA, WASDE, FOMC, ECB, CPI, NFP…)
- AI scorer injects SPECIALIST DESK context block for asset classes present
  in each scoring batch (upcoming reports, key drivers, regime sensitivity)
- /specialist-desks page: desk sidebar + fundamentals editor + macro
  sensitivity tag editor + reports tab + global reports catalogue + modal
  to create/edit any report with desk assignment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-23 09:57:18 +02:00
parent f2dd859ba0
commit 2fb683eec5
8 changed files with 1252 additions and 2 deletions

View File

@@ -1101,3 +1101,106 @@ export const useEvaluateInstrumentScan = () => {
onSuccess: () => qc.invalidateQueries({ queryKey: ['pattern-lab-runs'] }),
})
}
// ── Specialist Desks ───────────────────────────────────────────────────────────
export type MacroSensitivity = { regime: string; effect: string }
export type PriceDeltaThresholds = {
significant_day: number; extreme_day: number
significant_week: number; extreme_week: number
}
export type SpecialistReport = {
id: string; name: string; source: string; cadence: string
next_date: string | null; last_date: string | null
importance: number; notes: string; url: string
desks?: string[]
created_at: string; updated_at: string
}
export type DeskConfig = {
asset_class: string; display_name: string; icon: string
fundamentals: string; macro_sensitivity: MacroSensitivity[]
price_delta_thresholds: PriceDeltaThresholds; notes: string
reports: SpecialistReport[]; updated_at: string
}
export const useAllDeskConfigs = () =>
useQuery<DeskConfig[]>({
queryKey: ['desk-configs'],
queryFn: () => api.get('/specialist-desks/configs').then(r => r.data),
staleTime: 60_000,
})
export const useUpdateDeskConfig = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ asset_class, body }: { asset_class: string; body: Partial<DeskConfig> }) =>
api.put(`/specialist-desks/configs/${asset_class}`, body).then(r => r.data),
onSuccess: () => qc.invalidateQueries({ queryKey: ['desk-configs'] }),
})
}
export const useAllSpecialistReports = () =>
useQuery<SpecialistReport[]>({
queryKey: ['specialist-reports'],
queryFn: () => api.get('/specialist-desks/reports').then(r => r.data),
staleTime: 60_000,
})
export const useCreateSpecialistReport = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: (body: Partial<SpecialistReport> & { desks?: string[] }) =>
api.post('/specialist-desks/reports', body).then(r => r.data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['specialist-reports'] })
qc.invalidateQueries({ queryKey: ['desk-configs'] })
},
})
}
export const useUpdateSpecialistReport = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ id, body }: { id: string; body: Partial<SpecialistReport> & { desks?: string[] } }) =>
api.put(`/specialist-desks/reports/${id}`, body).then(r => r.data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['specialist-reports'] })
qc.invalidateQueries({ queryKey: ['desk-configs'] })
},
})
}
export const useDeleteSpecialistReport = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: (id: string) => api.delete(`/specialist-desks/reports/${id}`).then(r => r.data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['specialist-reports'] })
qc.invalidateQueries({ queryKey: ['desk-configs'] })
},
})
}
export const useLinkReportDesk = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ report_id, asset_class }: { report_id: string; asset_class: string }) =>
api.post(`/specialist-desks/reports/${report_id}/link/${asset_class}`).then(r => r.data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['specialist-reports'] })
qc.invalidateQueries({ queryKey: ['desk-configs'] })
},
})
}
export const useUnlinkReportDesk = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ report_id, asset_class }: { report_id: string; asset_class: string }) =>
api.delete(`/specialist-desks/reports/${report_id}/link/${asset_class}`).then(r => r.data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['specialist-reports'] })
qc.invalidateQueries({ queryKey: ['desk-configs'] })
},
})
}