{ "kind": "open-flow-workflow", "version": 1, "name": "SERPBear Ranking Report", "content": { "modelVersion": 14, "document": { "bindings": {}, "graph": { "nodes": { "n_72c073984cdc3ba5": { "name": "Run ranking report", "kind": "start", "inputDefs": [] }, "n_9f4c2a9aee9290e7": { "name": "Weekly ranking report", "kind": "cron", "cronTimes": [ { "type": "cron", "expression": "0 0 * * 0", "timezone": "UTC" } ] }, "n_940c471783ced2d1": { "inputs": { "domain": { "kind": "value", "value": "example.com" }, "blog": { "kind": "value", "value": "Example site" } }, "name": "Prepare report scope", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "domain" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "blog" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "domain" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "blog" }, { "jsonSchema": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "nullable": false, "handle": "reportDate" } ], "name": "Prepare report scope", "moduleId": "n_7f1d98494365e7a3", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_e2743385b8fbaea9": { "inputs": { "url": { "kind": "value", "value": "https://YOUR_SERPBEAR_HOST/api/keywords" }, "query.domain": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_940c471783ced2d1", "output": "domain" } ] }, "auth.token": { "kind": "unset" } }, "name": "Read SERPBear rankings", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "url" }, { "jsonSchema": { "anyOf": [ { "type": [ "string", "number", "boolean" ] }, { "type": "array", "items": { "type": [ "string", "number", "boolean" ] } } ] }, "nullable": true, "handle": "query.domain" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "auth.token" } ], "outputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "integer" }, "nullable": false, "handle": "statusCode" }, { "jsonSchema": { "type": "object", "additionalProperties": { "type": "string" } }, "nullable": false, "handle": "headers" } ], "name": "Read SERPBear rankings", "executor": { "method": "GET", "query": { "mode": "fields", "names": [ "domain" ] }, "headers": { "mode": "fields", "names": [] }, "bodyFormat": "none", "authentication": { "type": "bearer" }, "responseFormat": "json", "statusPolicy": "success", "kind": "http-request" } } }, "n_b327840555e8b372": { "inputs": { "body": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_e2743385b8fbaea9", "output": "body" } ] }, "domain": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_940c471783ced2d1", "output": "domain" } ] }, "reportDate": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_940c471783ced2d1", "output": "reportDate" } ] } }, "name": "Summarize ranking history", "kind": "task", "task": { "inputs": [ { "jsonSchema": {}, "nullable": true, "handle": "body" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "domain" }, { "jsonSchema": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "nullable": false, "handle": "reportDate" } ], "outputs": [ { "jsonSchema": { "type": "array", "items": { "type": "object" } }, "nullable": false, "handle": "summaries" }, { "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "role": { "type": "string", "enum": [ "system", "user", "assistant", "tool", "developer" ], "description": "The role of the message sender." }, "content": { "anyOf": [ { "type": "string", "description": "Plain text message content." }, { "type": "array", "items": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "A JSON object allowing any additional fields." }, "description": "Multimodal or structured message content blocks." } ], "description": "Message content." }, "name": { "type": "string", "description": "An optional name for the sender of the message." }, "tool_call_id": { "type": "string", "description": "The tool call ID corresponding to the tool message." } }, "required": [ "role", "content" ], "additionalProperties": true, "description": "A message in a chat completion request." }, "description": "An ordered list of conversation messages." }, "nullable": false, "handle": "messages" } ], "name": "Summarize ranking history", "moduleId": "n_d3ac11894e9f6b2f", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_b5be5af6dffcd741": { "inputs": { "model": { "kind": "value", "value": "meta-llama/llama-3.1-70b-instruct" }, "messages": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b327840555e8b372", "output": "messages" } ] } }, "name": "Analyze keyword rankings", "kind": "task", "task": { "inputs": [ { "description": "The model ID to use.", "jsonSchema": { "type": "string", "description": "The model ID to use." }, "nullable": false, "handle": "model" }, { "description": "An ordered list of conversation messages.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "role": { "type": "string", "enum": [ "system", "user", "assistant", "tool", "developer" ], "description": "The role of the message sender." }, "content": { "anyOf": [ { "type": "string", "description": "Plain text message content." }, { "type": "array", "items": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "A JSON object allowing any additional fields." }, "description": "Multimodal or structured message content blocks." } ], "description": "Message content." }, "name": { "type": "string", "description": "An optional name for the sender of the message." }, "tool_call_id": { "type": "string", "description": "The tool call ID corresponding to the tool message." } }, "required": [ "role", "content" ], "additionalProperties": true, "description": "A message in a chat completion request." }, "description": "An ordered list of conversation messages." }, "nullable": false, "handle": "messages" }, { "description": "The number of choices you wish to generate.", "jsonSchema": { "type": "integer", "minimum": 1, "description": "The number of choices you wish to generate." }, "nullable": true, "handle": "n", "value": null }, { "description": "Generation stops when encountering these sequences.", "jsonSchema": { "anyOf": [ { "type": "string", "description": "Single stop sequence." }, { "type": "array", "items": { "type": "string", "description": "A stop sequence." }, "description": "Multiple stop sequences." } ], "description": "Generation stops when encountering these sequences." }, "nullable": true, "handle": "stop", "value": null }, { "description": "The end user's unique identifier.", "jsonSchema": { "type": "string", "description": "The end user's unique identifier." }, "nullable": true, "handle": "user", "value": null }, { "description": "nucleus sampling parameters.", "jsonSchema": { "type": "number", "minimum": 0, "maximum": 1, "description": "nucleus sampling parameters." }, "nullable": true, "handle": "top_p", "value": null }, { "description": "Whether to request a streaming response. Currently connector action only supports false or omitted.", "jsonSchema": { "type": "boolean", "description": "Whether to request a streaming response. Currently connector action only supports false or omitted." }, "nullable": true, "handle": "stream", "value": null }, { "description": "A list of legacy functions that will be converted to tools at execution time.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string", "description": "The name of the function tool." }, "description": { "type": "string", "description": "Function tool description." }, "parameters": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "JSON Schema parameter definitions for function tools." } }, "required": [ "name" ], "additionalProperties": true, "description": "OpenAI style function tool definition." }, "description": "A list of legacy functions that will be converted to tools at execution time." }, "nullable": true, "handle": "functions", "value": null }, { "description": "Legacy function calling strategies are converted to tool_choice at execution time.", "jsonSchema": { "anyOf": [ { "type": "string", "enum": [ "none", "auto" ], "description": "Legacy function calling strategy." }, { "type": "object", "properties": { "name": { "type": "string", "description": "The name of the function to be forced to be called." } }, "required": [ "name" ], "additionalProperties": true, "description": "Legacy function calls select objects." } ], "description": "Legacy function calling strategies are converted to tool_choice at execution time." }, "nullable": true, "handle": "function_call", "value": null }, { "description": "Adjusts the bias map for the specified token sampling probability.", "jsonSchema": { "type": "object", "additionalProperties": { "type": "number", "description": "The offset value for this token." }, "description": "Adjusts the bias map for the specified token sampling probability." }, "nullable": true, "handle": "logit_bias", "value": null }, { "description": "The maximum number of output tokens.", "jsonSchema": { "type": "integer", "minimum": 1, "description": "The maximum number of output tokens." }, "nullable": true, "handle": "max_tokens", "value": null }, { "description": "New max output token field, taking precedence over max_tokens.", "jsonSchema": { "type": "integer", "minimum": 1, "description": "New max output token field, taking precedence over max_tokens." }, "nullable": true, "handle": "max_completion_tokens", "value": null }, { "description": "Sampling temperature.", "jsonSchema": { "type": "number", "minimum": 0, "maximum": 2, "description": "Sampling temperature." }, "nullable": true, "handle": "temperature", "value": null }, { "description": "There are penalties.", "jsonSchema": { "type": "number", "minimum": -2, "maximum": 2, "description": "There are penalties." }, "nullable": true, "handle": "presence_penalty", "value": null }, { "description": "Frequency penalty.", "jsonSchema": { "type": "number", "minimum": -2, "maximum": 2, "description": "Frequency penalty." }, "nullable": true, "handle": "frequency_penalty", "value": null }, { "description": "Whether to return token level probabilities.", "jsonSchema": { "type": "boolean", "description": "Whether to return token level probabilities." }, "nullable": true, "handle": "logprobs", "value": null }, { "description": "Returns the number of top logprobs.", "jsonSchema": { "type": "integer", "minimum": 0, "maximum": 20, "description": "Returns the number of top logprobs." }, "nullable": true, "handle": "top_logprobs", "value": null }, { "description": "A list of tools that can be called by the model.", "jsonSchema": { "type": "array", "items": { "type": "object", "properties": { "type": { "const": "function", "description": "Tool type, must be function." }, "function": { "type": "object", "properties": { "name": { "type": "string", "description": "The name of the function tool." }, "description": { "type": "string", "description": "Function tool description." }, "parameters": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "JSON Schema parameter definitions for function tools." } }, "required": [ "name" ], "additionalProperties": true, "description": "Function tool definition." } }, "required": [ "type", "function" ], "additionalProperties": true, "description": "OpenAI style tool definition." }, "description": "A list of tools that can be called by the model." }, "nullable": true, "handle": "tools", "value": null }, { "description": "Model how to select tools.", "jsonSchema": { "anyOf": [ { "type": "string", "enum": [ "none", "auto", "required" ], "description": "Predefined tool selection strategies." }, { "type": "object", "properties": { "type": { "const": "function", "description": "Tool selection type, must be function." }, "function": { "type": "object", "properties": { "name": { "type": "string", "description": "The name of the function facility to be forced to be called." } }, "required": [ "name" ], "additionalProperties": true, "description": "The function tool that is forced to be selected." } }, "required": [ "type", "function" ], "additionalProperties": true, "description": "Forces the configuration of the specified function tool to be called." } ], "description": "Model how to select tools." }, "nullable": true, "handle": "tool_choice", "value": null }, { "description": "Structured output format configuration.", "jsonSchema": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Structured output format configuration." }, "nullable": true, "handle": "response_format", "value": null }, { "description": "List of modalities to request output from.", "jsonSchema": { "type": "array", "items": { "type": "string", "description": "An output modal." }, "description": "List of modalities to request output from." }, "nullable": true, "handle": "modalities", "value": null }, { "description": "A list of candidate models if the main model fails.", "jsonSchema": { "type": "array", "items": { "type": "string", "description": "A candidate fallback model ID." }, "description": "A list of candidate models if the main model fails." }, "nullable": true, "handle": "models", "value": null }, { "description": "Request additional metadata.", "jsonSchema": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Request additional metadata." }, "nullable": true, "handle": "metadata", "value": null }, { "description": "provider routing preference configuration.", "jsonSchema": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "provider routing preference configuration." }, "nullable": true, "handle": "provider", "value": null }, { "description": "Plugin configuration to enable.", "jsonSchema": { "type": "array", "items": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "A JSON object allowing any additional fields." }, "description": "Plugin configuration to enable." }, "nullable": true, "handle": "plugins", "value": null }, { "description": "Service level.", "jsonSchema": { "type": "string", "description": "Service level." }, "nullable": true, "handle": "service_tier", "value": null }, { "description": "Session ID used for association requests.", "jsonSchema": { "type": "string", "description": "Session ID used for association requests." }, "nullable": true, "handle": "session_id", "value": null }, { "description": "Whether to allow parallel tool calls.", "jsonSchema": { "type": "boolean", "description": "Whether to allow parallel tool calls." }, "nullable": true, "handle": "parallel_tool_calls", "value": null }, { "description": "Additional options for streaming responses.", "jsonSchema": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Additional options for streaming responses." }, "nullable": true, "handle": "stream_options", "value": null }, { "description": "Inference model configuration.", "jsonSchema": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Inference model configuration." }, "nullable": true, "handle": "reasoning", "value": null } ], "outputs": [ { "description": "OpenRouter chat completion response.", "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Chat completion result ID." }, "object": { "type": "string", "description": "Return object type." }, "created": { "type": "integer", "description": "Unix timestamp of the time the result was created." }, "model": { "type": "string", "description": "The model ID used in this response." }, "choices": { "type": "array", "items": { "type": "object", "properties": { "index": { "type": "integer", "description": "The position of this choice in the result array." }, "message": { "type": "object", "properties": { "role": { "type": "string", "description": "Returns the role of the message." }, "content": { "anyOf": [ { "anyOf": [ { "type": "string", "description": "Text message content." }, { "type": "array", "items": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "A JSON object allowing any additional fields." }, "description": "List of structured content blocks." } ], "description": "The content of the returned message." }, { "type": "null" } ], "description": "The content of the returned message." }, "tool_calls": { "type": "array", "items": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "A JSON object allowing any additional fields." }, "description": "A list of tools called by the model request." }, "refusal": { "type": [ "string", "null" ], "description": "Description text returned when the model refuses to answer." } }, "required": [ "role" ], "additionalProperties": true, "description": "The message corresponding to this choice." }, "finish_reason": { "type": [ "string", "null" ], "description": "The reason why the model stopped outputting." }, "logprobs": { "type": [ "object", "null" ], "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Optional token-level probability information." } }, "required": [ "index", "message", "finish_reason" ], "additionalProperties": true, "description": "A choice in chat completion." }, "description": "The list of candidate results returned by the model." }, "usage": { "type": "object", "properties": { "prompt_tokens": { "type": "integer", "description": "Enter the number of tokens." }, "completion_tokens": { "type": "integer", "description": "Output the number of tokens." }, "total_tokens": { "type": "integer", "description": "Total number of tokens." }, "prompt_tokens_details": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Enter token segmentation information." }, "completion_tokens_details": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Output token breakdown information." } }, "required": [ "prompt_tokens", "completion_tokens", "total_tokens" ], "additionalProperties": true, "description": "The token usage of this response." } }, "required": [ "id", "object", "created", "model", "choices" ], "additionalProperties": true, "description": "OpenRouter chat completion response." }, "nullable": false, "handle": "output" } ], "name": "Analyze keyword rankings", "executor": { "kind": "connector", "action": "openrouter.create_chat_completion", "outputMode": "value" } } }, "n_ffcc486755015516": { "inputs": { "completion": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b5be5af6dffcd741", "output": "output" } ] }, "reportDate": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_940c471783ced2d1", "output": "reportDate" } ] }, "blog": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_940c471783ced2d1", "output": "blog" } ] } }, "name": "Prepare report row", "kind": "task", "task": { "inputs": [ { "jsonSchema": { "type": "object", "properties": { "id": { "type": "string", "description": "Chat completion result ID." }, "object": { "type": "string", "description": "Return object type." }, "created": { "type": "integer", "description": "Unix timestamp of the time the result was created." }, "model": { "type": "string", "description": "The model ID used in this response." }, "choices": { "type": "array", "items": { "type": "object", "properties": { "index": { "type": "integer", "description": "The position of this choice in the result array." }, "message": { "type": "object", "properties": { "role": { "type": "string", "description": "Returns the role of the message." }, "content": { "anyOf": [ { "anyOf": [ { "type": "string", "description": "Text message content." }, { "type": "array", "items": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "A JSON object allowing any additional fields." }, "description": "List of structured content blocks." } ], "description": "The content of the returned message." }, { "type": "null" } ], "description": "The content of the returned message." }, "tool_calls": { "type": "array", "items": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "A JSON object allowing any additional fields." }, "description": "A list of tools called by the model request." }, "refusal": { "type": [ "string", "null" ], "description": "Description text returned when the model refuses to answer." } }, "required": [ "role" ], "additionalProperties": true, "description": "The message corresponding to this choice." }, "finish_reason": { "type": [ "string", "null" ], "description": "The reason why the model stopped outputting." }, "logprobs": { "type": [ "object", "null" ], "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Optional token-level probability information." } }, "required": [ "index", "message", "finish_reason" ], "additionalProperties": true, "description": "A choice in chat completion." }, "description": "The list of candidate results returned by the model." }, "usage": { "type": "object", "properties": { "prompt_tokens": { "type": "integer", "description": "Enter the number of tokens." }, "completion_tokens": { "type": "integer", "description": "Output the number of tokens." }, "total_tokens": { "type": "integer", "description": "Total number of tokens." }, "prompt_tokens_details": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Enter token segmentation information." }, "completion_tokens_details": { "type": "object", "additionalProperties": { "description": "The original value corresponding to the field." }, "description": "Output token breakdown information." } }, "required": [ "prompt_tokens", "completion_tokens", "total_tokens" ], "additionalProperties": true, "description": "The token usage of this response." } }, "required": [ "id", "object", "created", "model", "choices" ], "additionalProperties": true, "description": "OpenRouter chat completion response." }, "nullable": false, "handle": "completion" }, { "jsonSchema": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" }, "nullable": false, "handle": "reportDate" }, { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "blog" } ], "outputs": [ { "jsonSchema": { "type": "string" }, "nullable": false, "handle": "report" }, { "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Baserow row payload keyed by field name or field ID." }, "nullable": false, "handle": "row" } ], "name": "Prepare report row", "moduleId": "n_de87260a82bd4952", "capabilities": [ { "kind": "connector", "mode": "independent", "actions": [] } ] } }, "n_a43a2645802d5460": { "inputs": { "tableId": { "kind": "value", "value": 1 }, "userFieldNames": { "kind": "value", "value": true }, "row": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_ffcc486755015516", "output": "row" } ] } }, "name": "Save ranking report", "kind": "task", "task": { "inputs": [ { "description": "The Baserow table ID.", "jsonSchema": { "type": "integer", "exclusiveMinimum": 0, "description": "The Baserow table ID." }, "nullable": true, "handle": "tableId", "value": null }, { "description": "Whether Baserow should use user-facing field names instead of field IDs.", "jsonSchema": { "type": "boolean", "description": "Whether Baserow should use user-facing field names instead of field IDs." }, "nullable": true, "handle": "userFieldNames", "value": null }, { "description": "A Baserow row payload keyed by field name or field ID.", "jsonSchema": { "type": "object", "properties": {}, "additionalProperties": true, "description": "A Baserow row payload keyed by field name or field ID." }, "nullable": false, "handle": "row" } ], "outputs": [ { "description": "The created Baserow row.", "jsonSchema": { "type": "object", "properties": { "id": { "type": "integer", "description": "The Baserow row ID." } }, "required": [ "id" ], "additionalProperties": true, "description": "The created Baserow row." }, "nullable": false, "handle": "row" } ], "name": "Save ranking report", "executor": { "kind": "connector", "action": "baserow.create_table_row" } } }, "n_f412a966a4a085d6": { "name": "Saved ranking report", "kind": "outputs", "outputDefs": [ { "jsonSchema": { "type": "object", "properties": { "id": { "type": "integer", "description": "The Baserow row ID." } }, "required": [ "id" ], "additionalProperties": true, "description": "The created Baserow row." }, "nullable": false, "handle": "row" }, { "jsonSchema": { "type": "array", "items": { "type": "object" } }, "nullable": false, "handle": "summaries" } ], "outputMappings": { "row": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_a43a2645802d5460", "output": "row" } ] }, "summaries": { "kind": "sources", "sources": [ { "kind": "node", "nodeId": "n_b327840555e8b372", "output": "summaries" } ] } } } }, "edges": [ { "source": "n_72c073984cdc3ba5", "target": "n_940c471783ced2d1" }, { "source": "n_9f4c2a9aee9290e7", "target": "n_940c471783ced2d1" }, { "source": "n_940c471783ced2d1", "target": "n_e2743385b8fbaea9" }, { "source": "n_e2743385b8fbaea9", "target": "n_b327840555e8b372" }, { "source": "n_b327840555e8b372", "target": "n_b5be5af6dffcd741" }, { "source": "n_b5be5af6dffcd741", "target": "n_ffcc486755015516" }, { "source": "n_ffcc486755015516", "target": "n_a43a2645802d5460" }, { "source": "n_a43a2645802d5460", "target": "n_f412a966a4a085d6" } ] } }, "modules": { "n_7f1d98494365e7a3": { "name": "Prepare report scope", "imports": [], "source": "export default function ({domain, blog}) {\n if (typeof domain !== 'string' || !domain.trim() || /[\\s\\/@:?#]/u.test(domain.trim())) throw new Error('Set the tracked SERPBear domain, without a protocol or path.');\n if (typeof blog !== 'string' || !blog.trim()) throw new Error('Set the Baserow Blog label.');\n return {domain: domain.trim(), blog: blog.trim(), reportDate: new Date().toISOString().slice(0, 10)};\n}\n" }, "n_d3ac11894e9f6b2f": { "name": "Summarize ranking history", "imports": [], "source": "export default function ({body, domain, reportDate}) {\n if (!body || typeof body !== 'object' || !Array.isArray(body.keywords)) throw new Error('SERPBear did not return a keywords array.');\n const summaries = body.keywords.map((keyword, index) => {\n if (!keyword || typeof keyword !== 'object' || typeof keyword.keyword !== 'string' || !keyword.keyword.trim()) throw new Error('Keyword ' + index + ' has no keyword text.');\n if (keyword.domain !== domain) throw new Error('A keyword does not match the configured domain.');\n if (!Number.isInteger(keyword.position) || keyword.position < 0) throw new Error('A keyword has an invalid position.');\n const history = keyword.history ?? {};\n if (typeof history !== 'object' || Array.isArray(history)) throw new Error('A keyword has invalid history.');\n const observations = Object.entries(history).map(([date, position]) => {\n const parsed = /^\\d{4}-\\d{2}-\\d{2}$/.test(date) ? new Date(date + 'T00:00:00Z') : null;\n if (!parsed || !Number.isFinite(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== date || !Number.isInteger(position) || position < 0) throw new Error('A keyword has an invalid history observation.');\n return {date, position};\n }).sort((a, b) => a.date.localeCompare(b.date)).slice(-7);\n const ranked = observations.filter(observation => observation.position > 0);\n const mean = ranked.length ? ranked.reduce((sum, observation) => sum + observation.position, 0) / ranked.length : null;\n const currentPosition = keyword.position === 0 ? null : keyword.position;\n const trend = currentPosition === null ? 'not in top 100' : mean === null ? 'insufficient ranked history' : currentPosition < mean ? 'improving' : currentPosition > mean ? 'declining' : 'stable';\n return {keyword: keyword.keyword, device: typeof keyword.device === 'string' ? keyword.device : '', country: typeof keyword.country === 'string' ? keyword.country : '', currentPosition, averagePosition: mean === null ? null : Math.round(mean * 10) / 10, observations: observations.length, rankedObservations: ranked.length, trend, url: typeof keyword.url === 'string' && keyword.url ? keyword.url : 'not ranking'};\n });\n const prompt = `Here's the SEO ranking data for ${domain} as of ${reportDate} (UTC):\\n\\n${summaries.length ? summaries.map(keyword => `Keyword: ${JSON.stringify(keyword.keyword)}\\nDevice: ${keyword.device}\\nCountry: ${keyword.country}\\nCurrent Position: ${keyword.currentPosition ?? 'not in top 100'}\\nLatest-7-observation Ranked Average: ${keyword.averagePosition ?? 'no ranked history'}\\nObservations: ${keyword.observations}, ranked: ${keyword.rankedObservations}\\nTrend: ${keyword.trend}\\nRanking URL: ${keyword.url}\\n`).join('\\n') : 'No tracked keywords were returned for this domain. Do not invent keyword rankings.'}\\n\\nAn observation with position 0 means not in the top 100. The average excludes these observations; no numeric average is claimed for unranked history. These are the latest available history observations, not necessarily seven consecutive calendar days.\\n\\nPlease analyze this data and provide:\\n1. Key observations about ranking performance\\n2. Keywords showing the most improvement\\n3. Keywords needing attention\\n4. Suggested actions for improvement`;\n return {summaries, messages: [{role: 'user', content: 'You are an SEO expert. This is keyword data for my site. Can you summarize the data into a table and then give me some suggestions:' + prompt}]};\n}\n" }, "n_de87260a82bd4952": { "name": "Prepare report row", "imports": [], "source": "export default function ({completion, reportDate, blog}) {\n const report = completion?.choices?.[0]?.message?.content;\n if (typeof report !== 'string' || !report.trim()) throw new Error('OpenRouter did not return report text.');\n return {report, row: {Date: reportDate, Note: report, Blog: blog}};\n}\n" } } }, "presentation": { "designer": { "version": 1, "flow": { "direction": "vertical" } } } }