SKILL.md
As tools abaixo aparecem com os nomes que o MCP da Clickmax registra. Se o seu cliente de IA prefixar nomes de tool (
mcp<servidor>,mcp<servidor>, ou outro), use o nome já prefixado que aparecer na sua lista de tools.
When this applies
Use this skill to create, edit or read a saved Insights dashboard: assembling a board of opportunity/activity/lead widgets, starting from a ready-made template, changing a widget's chart or breakdown, reordering the grid, or answering "what do the numbers on my dashboard say".
Not this skill:
- one-off revenue/lead/funnel KPI questions with no saved board ->
clickmax-analytics - what is selling: top offers, order-bump attach, sales trend ->
clickmax-sales-insights - payment dashboard browsing and loss/recovery cohorts ->
clickmax-payments-dashboard-analysis - moving cards, stages, attendants, pipeline settings ->
clickmax-pipelines - per-lead timelines and owner activity aggregates ->
clickmax-leads-activity-analysis
A dashboard is a saved artifact. If the user just wants a number once, answer with the analytics skills above instead of creating a board.
Key assumptions
- Widget metrics are a closed curated catalog, not free text. A raw
widgetwhoseentity/reportTypeno engine runs is refused on write with 400 listing the validreportTypes for that entity — it is not saved as a broken card. Never invent a metric name. - A parameterized lead segment must carry its parameter in the same call:
segmentBy: customField→customFieldId,segmentBy: tag|cardTag→tagIds. Missing it = 400 on write (widgetadd/widgetupdate/compose), not an empty card to fix later. - Filters live on the dashboard, not on the widget. One filter set applies to every widget at once; there is no per-widget filter. Cuts like open/won/lost are expressed by the widget's own
viewBy, never by filtering status. filtersonly accepts what the screen can show:dateFrom/dateTo(period) pluspipelineIds/attendantIds(pipeline/owner scope). Every other key —statuses,stageIds,temperatures,priorities,valueMin,valueMax,origins,taskTypes,taskStatuses— is 400 on write (create/compose/settings_update), and the error names the alternative: negotiation cuts go inconditions, open/won/lost goes in the widget'sviewBy. Reason: saved there, the cut would trim every card with no control on the screen to see or clear it.periodPresetis resolved at run time, on both ends (screen and backend, BRT clock): a dynamic preset (today…currentMonth) re-resolves the window and OVERWRITESdateFrom/dateToon every run;all/month/customuse the saved dates. Sending only the preset (no dates) is the right path — therunanswers the preset's window, not the whole history.- The shared filter is one set, but each metric applies only the keys of ITS vocabulary —
taskTypesmeans nothing to a deal,temperaturesnothing to an activity, andforecastneutralizesstatuseson purpose (astatuses:['open']would zero out the realized half). The run reports what it dropped per widget inignoredFilters: read it instead of assuming the whole filter landed. pipelineIdon a widget is the one exception, and it is scope, not filter: metrics broken down by stage are relative to a single pipeline, so two stage widgets on the same board can look at different pipelines. And it is required on write: a stage-broken metric (progress,stagetime,stageflow,stageConversion,lossBreakdown, orsegmentBy: 'stage') withoutpipelineId= 400 oncompose/widgetadd/widgetupdate. Without it the card would fall back to the default pipeline (the most recent one) and the number gets read as the whole operation's. List the pipelines first (pipeline tools) or sendpipelineIdin thecomposebody — it covers every stage widget of that assembly.config.conditionsis the advanced filter — an opportunity-filter tree, separate from the flatconfig.filters, and flattened viaparentId(never nested objects). Group node =field: 'children'+operator: childrenAnd|childrenOr, and it MUST carryidbecause leaves link to it byparentId; a group withoutidcollapses the tree into a flat AND. Scope: applied bydeal:andactivity:, ignored bylead:*→ the run flags that widget withignoredAdvancedFilter: true.- Value encoding per operator, and getting it wrong is not a silent no-op — the compiler throws and every widget that applies conditions degrades to
status: 'error'with a zero:
|Operator|Column|Encoding| |-|-|-| |in / notIn|valueString|JSON array string: '["<uuid>"]' — never a bare '<uuid>'| |equals on enum/text|valueString|raw value: 'won'| |date comparisons|valueDate|ISO 8601| |number comparisons|valueNumber|number| |boolean|valueBool|boolean|
- Caps: 25 dashboards per workspace, 24 widgets per dashboard — and a section title band spends one of those 24. Both refuse with a permission-style error, not a validation error.
- Filter caps (validation errors): each
filterslist ≤ 100 items (originsstrings ≤ 255 chars) |conditions≤ 200 nodes, nested ≤ 20 group levels. Filter by the ids that matter instead of pasting the whole workspace. - Layout is part of the artifact, not decoration:
sizeis{w,h}on a 12-column grid (w3..12,h1..8) and a section title band is a widget entry carryingheadinginstead of a chart. Omitsizeand the card falls back to the default for itschartType(KPI 3×2, pie/ranking/list 4×4, bar/line 6×4, table 12×5) — which is why a board of three KPIs assembled one by one leaves a quarter of the screen empty.composecloses those rows for you. - Result units are not uniform: monetary measures come back in cents,
duration/stagetimein days,conversion/completionas a 0..1 rate. Convert before showing money or percentages. - Running a dashboard is stateless — the config does not have to be saved. Use it to preview a board before creating it.
- A widget whose selection cannot run degrades to an empty result instead of failing the call — and says so:
status: 'error'on that widget. Trust the field, never the zero: an all-zero widget withstatus: 'ok'is a real "no results for this cut". - Deleting a dashboard removes only the saved view; opportunity data is untouched.
Thought process
- Read the catalog before proposing anything — it is the only source of valid metrics and template ids.
- Prefer a template when the ask is broad ("um painel pra acompanhar o comercial"). When the user named the metrics, build the board with
composein ONE call — widget-by-widget is for editing a board that already exists. - Decide whether the user wants an artifact (create/save) or an answer (run only).
- For edits, address the single widget instead of resending the whole board.
- Confirm before deleting a dashboard or replacing a whole config.
Execute guide
- Always start with connector operation
analyticsdashboardscatalog. It returns the valid widget presets (each with a stable id, group, label and the metric selection behind it), the dashboard templates, and the current limits. Everything below depends on ids that come from here.
- Broad ask -> template. Use
analyticsdashboardscreatefromtemplatewith thetemplateIdfrom the catalog, optionallynameandpipelineId(the pipeline for the template's stage metrics, STAMPED into each widget; absent = the most recent pipeline, stamped the same way — so the board never switches pipeline on its own later). One call produces a coherent board with its widgets already configured — prefer it over assembling widget by widget, which costs many calls and usually lands on a worse selection.
- Named metrics ->
analyticsdashboardscompose. Declare the board insections: each section is{ heading?, widgets: [{ presetId | widget, title?, pipelineId?, size?, customFieldId?, tagIds? }] }. One call resolves every preset, inserts the section title bands and sizes the cards so the grid rows close. Leave the first section without aheading— that is the KPI band that opens the board.pipelineIdat the top level is the default scope for stage-broken metrics; an item may override it.filters,periodPreset,showGoalsandconditionsare set in the same call.autoSize: falseonly when the user wants the raw defaults.
- Escape hatches for creating.
analyticsdashboardscreatetakes a wholeconfig(each widget needs a client-generated uuidid) — use it to reproduce an exact config you already have. A blank board (widgets: [],filters: {}) pluswidget_addcalls still works and validates one metric at a time, but it costs N calls and lands on a worse layout thancompose.
- Add a widget with
analyticsdashboardswidget_add, passing the dashboard id plus exactly one of:presetId(preferred — the metric selection and the title are seeded from the catalog), a rawwidgetselection for a variation the catalog does not enumerate, orheadingto insert a section title band.titleoverrides the seeded label,positionis the 0-based insertion index (default: last),pipelineIdscopes a stage-broken metric,sizesets the card on the 12-column grid, andcustomFieldId/tagIdsfill the parameter of a lead segment (required wheneversegmentByiscustomField|tag|cardTag— the call is refused without it).
- Change one widget with
analyticsdashboardswidgetupdate(dashboard id + widget id): only the keys you send change. This is the way to switchchartTypebetween bar, line, pie, number, ranking and list, to retitle, or to changesegmentBy/viewBy/interval. Validation runs on the MERGED widget: switching onlyreportTypeon a lead widget to a deal-only report is a 400, and switchingsegmentByto a parameterized one requires sending its parameter in the same call. Remove withanalyticsdashboardswidgetremove, reorder withanalyticsdashboardswidget_move— the widget order IS the display order in the grid.
- Board settings (shared
filters,periodPreset,showGoals, advancedconditions,liveTV mode) change withanalyticsdashboardssettings_update— partial merge, widgets untouched: only the keys you send change.
- Do not reach for
analyticsdashboardsupdateto touch a widget or a setting: it replaces the whole config and drops any concurrent change. Use it only to rename a dashboard.
- Read the numbers with
analyticsdashboardsrun, passing aconfig— take it fromanalyticsdashboardslistfor a saved board, or pass a candidate config to preview one before saving. Each widget comes back withbuckets,seriesand asummary.total.
- Before creating, check
meta.countagainstmeta.limitfromanalyticsdashboardslistwhen the workspace looks crowded, so you can offer to delete one instead of hitting the cap mid-flow.
Report
- After creating: name the dashboard, say which template (if any) it came from, and list the sections and the widgets each one has — do not dump the raw config or the sizes.
- After a widget edit: state what changed on which widget, not the whole board.
- When reporting numbers: convert cents to currency, rates to percentages, and durations to days before showing them. Lead with the takeaway, then the per-widget values.
- A widget with
status: 'error': say the selection failed and offer a catalog alternative — never report its zero as a business result. Zeros withstatus: 'ok'are real, even next to populated siblings. - When a widget lists
ignoredFilters, say which cut did not reach it ("the period does not apply to overdue activities — it is a snapshot of now"). Silently presenting the number as filtered is how the user ends up trusting the wrong figure. - Deleting a dashboard is opt-in only; confirm before doing it.
Warnings
- Never guess a
presetIdortemplateId; both come from the catalog and nothing else. - Do not express open/won/lost by filtering status — that is what
viewByis for, and the filter would hit every other widget on the board too. - A stage-broken widget without
pipelineIdfalls back to a default pipeline; set it explicitly when the user named a pipeline. - Money is in cents everywhere in the run result. Reporting the raw integer as currency is off by 100x.
Anti-patterns
- Assembling ten widgets by hand when a template answers the same ask in one call — or when
composebuilds the named board in one call. - Building a board of six cards with no section band and no sizes: it renders as a column of unrelated cards with open rows.
- Resending the whole config to change the period or turn goals on.
- Creating a saved dashboard when the user only asked for a number once.
- Resending the whole config to change one chart type.
- Reporting a widget with
status: 'error'as a real business result — or calling a legitimate zero an invalid selection. - Presenting a number as filtered when the widget reported that cut in
ignoredFilters. - Inventing a metric name instead of reading the catalog — a raw
widgetwith a combination no engine runs now fails the call instead of landing as a broken card. - Writing a cut into
filtersbeyond period/pipeline/owner (it is a 400) instead ofconditionsor the widget'sviewBy. - Assembling a stage widget without
pipelineId(it is a 400) — ask which pipeline, or list them first. - Hand-computing
dateFrom/dateTowhen the ask is "this month"/"last 7 days": sendperiodPresetand let the run resolve it. - Switching a widget to
segmentBy: customField|tag|cardTagwithout sendingcustomFieldId/tagIdsin the same call.