{
  "version": 3,
  "sources": ["twenty-sdk-define-stub:__twenty-sdk-define-stub__", "../../../../src/constants/universal-identifiers.ts", "../../../../src/lib/backfill.ts", "../../../../src/lib/score-record.ts", "../../../../src/lib/enrich.ts", "../../../../src/logic-functions/backfill.function.ts"],
  "sourcesContent": ["\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n  success: true,\n  config,\n  errors: [],\n});\n\nconst __anyHandler = {\n  get(_target, prop) {\n    if (prop === '__esModule') return true;\n    if (prop === Symbol.toPrimitive) return () => '';\n    if (typeof prop === 'symbol') return undefined;\n    return new Proxy(() => undefined, __anyHandler);\n  },\n  apply() {\n    return new Proxy(() => undefined, __anyHandler);\n  },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineIndex = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePermissionFlag = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const defineViewField = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PageLayoutType = __anyStub;\nexport const RelationType = __anyStub;\nexport const RowLevelPermissionPredicateGroupLogicalOperator = __anyStub;\nexport const RowLevelPermissionPredicateOperand = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const STANDARD_PAGE_LAYOUT = __anyStub;\nexport const STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const SystemPermissionFlag = __anyStub;\nexport const ViewCalendarLayout = __anyStub;\nexport const ViewFilterGroupLogicalOperator = __anyStub;\nexport const ViewFilterOperand = __anyStub;\nexport const ViewKey = __anyStub;\nexport const ViewOpenRecordIn = __anyStub;\nexport const ViewSortDirection = __anyStub;\nexport const ViewType = __anyStub;\nexport const ViewVisibility = __anyStub;\nexport const canAccessFullAdminPanel = __anyStub;\nexport const canImpersonate = __anyStub;\nexport const every = __anyStub;\nexport const everyDefined = __anyStub;\nexport const everyEquals = __anyStub;\nexport const favoriteRecordIds = __anyStub;\nexport const featureFlags = __anyStub;\nexport const getFieldUniversalIdentifier = __anyStub;\nexport const hasAnySoftDeleteFilterOnView = __anyStub;\nexport const includes = __anyStub;\nexport const includesEvery = __anyStub;\nexport const isDashboardPageLayoutInEditMode = __anyStub;\nexport const isDefined = __anyStub;\nexport const isInSidePanel = __anyStub;\nexport const isLayoutCustomizationModeEnabled = __anyStub;\nexport const isNonEmptyString = __anyStub;\nexport const isSelectAll = __anyStub;\nexport const none = __anyStub;\nexport const noneDefined = __anyStub;\nexport const noneEquals = __anyStub;\nexport const numberOfSelectedRecords = __anyStub;\nexport const objectMetadataItem = __anyStub;\nexport const objectMetadataLabel = __anyStub;\nexport const objectPermissions = __anyStub;\nexport const pageType = __anyStub;\nexport const selectedRecords = __anyStub;\nexport const some = __anyStub;\nexport const someDefined = __anyStub;\nexport const someEquals = __anyStub;\nexport const someNonEmptyString = __anyStub;\nexport const targetObjectReadPermissions = __anyStub;\nexport const targetObjectWritePermissions = __anyStub;\nexport const validateFields = __anyStub;\n", "export const APP_DISPLAY_NAME = 'Lead Scoring Agent';\nexport const APP_DESCRIPTION = 'AI lead scoring and enrichment agent for Twenty CRM';\nexport const APPLICATION_UNIVERSAL_IDENTIFIER = '94f2e86e-62e5-4695-b4e8-50059cd7db30';\nexport const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = 'c9a59ba6-03be-41f8-9ef3-3fc858fbd8ae';\nexport const MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =\n  '90e55e60-286b-49de-a2ce-6303afaae532';\nexport const MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = 'dbec88c2-9609-4d9a-8f04-4da5fa4325d5';\nexport const MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = 'b3090e7f-59ea-4cc3-afb5-dc9396e19756';\nexport const MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER = '721c32d7-e919-4ab3-af47-8f83ce3813d8';\nexport const MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER =\n  '05883ee0-85f0-47cc-ac55-958be6a6e502';\n\n// Lead-scoring custom fields (TCA-3)\nexport const PERSON_LEAD_SCORE_FIELD_UNIVERSAL_IDENTIFIER =\n  'f5374be3-4752-4eb1-aaee-aeaa99782274';\nexport const PERSON_LEAD_SCORE_SUMMARY_FIELD_UNIVERSAL_IDENTIFIER =\n  '8a2cf015-3059-483a-b655-9b4c8381a2de';\nexport const PERSON_LEAD_SCORED_AT_FIELD_UNIVERSAL_IDENTIFIER =\n  'b56cb686-455c-42ea-8a42-eb64d45aeb70';\nexport const PERSON_LEAD_SCORE_STATUS_FIELD_UNIVERSAL_IDENTIFIER =\n  '3b99d41b-67f6-4436-ae53-deb9f5d6a70f';\nexport const OPPORTUNITY_LEAD_SCORE_FIELD_UNIVERSAL_IDENTIFIER =\n  '37831211-183e-4766-b643-d4539c79e16b';\nexport const OPPORTUNITY_LEAD_SCORE_SUMMARY_FIELD_UNIVERSAL_IDENTIFIER =\n  '44c4fa1e-a1c9-4a7d-bf10-8e3d8ed0ce0a';\nexport const OPPORTUNITY_LEAD_SCORED_AT_FIELD_UNIVERSAL_IDENTIFIER =\n  '0c29ee13-9fd6-4cf1-812c-921861845941';\nexport const OPPORTUNITY_LEAD_SCORE_STATUS_FIELD_UNIVERSAL_IDENTIFIER =\n  'aecb4dd2-514a-49fa-a9fc-76d8a903714a';\n\n// Lead-scorer agent (TCA-4)\nexport const LEAD_SCORER_AGENT_UNIVERSAL_IDENTIFIER =\n  '9d7079b1-cb1f-473e-8cf3-69614f382254';\n\n// Logic functions: scoreOnEvent (TCA-9) + drainQueue (TCA-10)\nexport const PERSON_SCORE_ON_CREATED_FUNCTION_UNIVERSAL_IDENTIFIER =\n  '3a44b88b-a56f-4693-8968-33b9215407fc';\nexport const PERSON_SCORE_ON_UPDATED_FUNCTION_UNIVERSAL_IDENTIFIER =\n  '2a879207-f1c2-409a-90fe-636133ce4446';\nexport const OPPORTUNITY_SCORE_ON_CREATED_FUNCTION_UNIVERSAL_IDENTIFIER =\n  '9130ed95-9c76-49a5-8722-2b3e7b52d640';\nexport const OPPORTUNITY_SCORE_ON_UPDATED_FUNCTION_UNIVERSAL_IDENTIFIER =\n  'efcf62de-260b-4583-9c2d-a14e7296d13c';\nexport const DRAIN_QUEUE_FUNCTION_UNIVERSAL_IDENTIFIER =\n  'a68d29b9-eb06-41d5-9416-52498f6fd41f';\n\n// Leads views sorted by leadScore desc (TCA-8)\nexport const PERSON_LEADS_VIEW_UNIVERSAL_IDENTIFIER =\n  '1120ee21-41a8-4af0-9a26-6cae1a7432cd';\nexport const PERSON_LEADS_VIEW_SORT_UNIVERSAL_IDENTIFIER =\n  '56341e06-9015-452b-a802-6d0bbade0b87';\nexport const OPPORTUNITY_LEADS_VIEW_UNIVERSAL_IDENTIFIER =\n  '97f70757-8635-4f98-8281-b1bc0182f19d';\nexport const OPPORTUNITY_LEADS_VIEW_SORT_UNIVERSAL_IDENTIFIER =\n  '50125f82-1d24-4a86-856d-1717763c455c';\n\n// Standard field universal identifiers (context columns in leads views, TCA-8)\nexport const PERSON_NAME_FIELD_UNIVERSAL_IDENTIFIER =\n  '20202020-3875-44d5-8c33-a6239011cab8';\nexport const PERSON_JOB_TITLE_FIELD_UNIVERSAL_IDENTIFIER =\n  '20202020-b0d0-415a-bef9-640a26dacd9b';\nexport const PERSON_COMPANY_FIELD_UNIVERSAL_IDENTIFIER =\n  '20202020-e2f3-448e-b34c-2d625f0025fd';\nexport const OPPORTUNITY_NAME_FIELD_UNIVERSAL_IDENTIFIER =\n  '20202020-8609-4f65-a2d9-44009eb422b5';\nexport const OPPORTUNITY_AMOUNT_FIELD_UNIVERSAL_IDENTIFIER =\n  '20202020-583e-4642-8533-db761d5fa82f';\nexport const OPPORTUNITY_STAGE_FIELD_UNIVERSAL_IDENTIFIER =\n  '20202020-6f76-477d-8551-28cd65b2b4b9';\n\n// Leads view fields (visible columns, TCA-8)\nexport const PERSON_LEADS_VIEW_FIELD_NAME_UNIVERSAL_IDENTIFIER =\n  'eb70632e-53c1-4184-a0ce-0fcab78e93c7';\nexport const PERSON_LEADS_VIEW_FIELD_JOB_TITLE_UNIVERSAL_IDENTIFIER =\n  '40a34cb1-df20-46d0-a6a9-d0fe356fa23c';\nexport const PERSON_LEADS_VIEW_FIELD_COMPANY_UNIVERSAL_IDENTIFIER =\n  '41c3f224-d5b2-42cf-87f8-ff2d3f86ddb3';\nexport const PERSON_LEADS_VIEW_FIELD_LEAD_SCORE_UNIVERSAL_IDENTIFIER =\n  '93d0d121-54c6-4aab-83fc-e519837ead26';\nexport const PERSON_LEADS_VIEW_FIELD_LEAD_SCORE_STATUS_UNIVERSAL_IDENTIFIER =\n  '17d29dc8-8e67-4080-8003-3df2baab8c24';\nexport const OPPORTUNITY_LEADS_VIEW_FIELD_NAME_UNIVERSAL_IDENTIFIER =\n  '5c1ae67c-67ac-4799-be80-1aad69e9fa60';\nexport const OPPORTUNITY_LEADS_VIEW_FIELD_AMOUNT_UNIVERSAL_IDENTIFIER =\n  '39d02d43-ec15-4023-a475-16637cc57841';\nexport const OPPORTUNITY_LEADS_VIEW_FIELD_STAGE_UNIVERSAL_IDENTIFIER =\n  '9de6325a-5598-4767-b985-440ec024c17f';\nexport const OPPORTUNITY_LEADS_VIEW_FIELD_LEAD_SCORE_UNIVERSAL_IDENTIFIER =\n  'e78fb4e2-fa89-4311-8c4d-c2b6e11a2c02';\nexport const OPPORTUNITY_LEADS_VIEW_FIELD_LEAD_SCORE_STATUS_UNIVERSAL_IDENTIFIER =\n  '2df313ba-eca2-4621-8998-435a2d7ed31b';\n\n// Backfill logic function (TCA-12)\nexport const BACKFILL_FUNCTION_UNIVERSAL_IDENTIFIER =\n  '4e419acc-e5b1-4346-a4c6-7262851fc507';\n", "/**\n * Backfill for pre-existing unscored leads (TCA-12).\n *\n * The scoring pipeline only fires on database events (create/update), so\n * records that existed before the app was installed never get a score. This\n * helper finds people + opportunities that still need scoring \u2014 never-scored\n * (null status) OR previously-FAILED (TCA-13) \u2014 and enqueues them by writing\n * status QUEUED. The existing `drainQueue` cron then scores each queued record\n * via the shared `scoreRecord` pipeline.\n *\n * Enqueue (vs. inline scoring) is deliberate: scoring runs an AI agent per\n * record and a full backfill could exceed a single logic-function timeout.\n * Marking QUEUED is a cheap bounded write; the cron drains in capped batches.\n *\n * Including FAILED lets a failure left by a transient incident (e.g. credit\n * exhaustion) be re-scored once credits return, without a manual DB edit. Since\n * credit exhaustion now re-queues itself (see `scoreRecord`), FAILED means a\n * genuine error; re-queueing here is operator-triggered (not the cron), so a\n * broken record is retried only on an explicit backfill, not in a tight loop.\n * A bounded retry-count field remains the proper long-term guard (TCA-10).\n *\n * Idempotent: re-running re-queues only records that still need scoring, never\n * ones already QUEUED / SCORED / SKIPPED.\n */\nimport { CoreApiClient } from 'twenty-client-sdk/core';\n\nimport {\n  type ScoreObjectType,\n  setLeadScoreStatus,\n} from 'src/lib/score-record';\n\nconst BACKFILL_BATCH_LIMIT = 50;\n\nconst fetchUnscoredIds = async (\n  objectType: ScoreObjectType,\n): Promise<string[]> => {\n  const client = new CoreApiClient();\n\n  if (objectType === 'person') {\n    const { people } = await client.query({\n      people: {\n        __args: {\n          first: BACKFILL_BATCH_LIMIT,\n          filter: {\n            or: [\n              { leadScoreStatus: { is: 'NULL' } },\n              { leadScoreStatus: { eq: 'FAILED' } },\n            ],\n          },\n        },\n        edges: { node: { id: true } },\n      },\n    });\n\n    return (people?.edges ?? [])\n      .map((edge) => edge?.node?.id)\n      .filter((id): id is string => typeof id === 'string');\n  }\n\n  const { opportunities } = await client.query({\n    opportunities: {\n      __args: {\n        first: BACKFILL_BATCH_LIMIT,\n        filter: {\n          or: [\n            { leadScoreStatus: { is: 'NULL' } },\n            { leadScoreStatus: { eq: 'FAILED' } },\n          ],\n        },\n      },\n      edges: { node: { id: true } },\n    },\n  });\n\n  return (opportunities?.edges ?? [])\n    .map((edge) => edge?.node?.id)\n    .filter((id): id is string => typeof id === 'string');\n};\n\nconst enqueueAll = async (\n  objectType: ScoreObjectType,\n  ids: string[],\n): Promise<number> => {\n  for (const recordId of ids) {\n    await setLeadScoreStatus(objectType, recordId, 'QUEUED');\n  }\n\n  return ids.length;\n};\n\nexport type BackfillResult = {\n  people: number;\n  opportunities: number;\n  total: number;\n};\n\n/**\n * Enqueue every unscored person + opportunity for scoring. Returns how many of\n * each were queued (they will be scored on the next `drainQueue` cron run).\n */\nexport const backfillUnscored = async (): Promise<BackfillResult> => {\n  const [personIds, opportunityIds] = await Promise.all([\n    fetchUnscoredIds('person'),\n    fetchUnscoredIds('opportunity'),\n  ]);\n\n  const [people, opportunities] = await Promise.all([\n    enqueueAll('person', personIds),\n    enqueueAll('opportunity', opportunityIds),\n  ]);\n\n  return { people, opportunities, total: people + opportunities };\n};\n", "/**\n * Shared scoring path (TCA-9 / TCA-10).\n *\n * `scoreRecord` runs the full enrich -> build prompt -> runAgent -> write\n * pipeline for a single person or opportunity and is reused by both the\n * database-event handler (inline scoring) and the cron drain (queued scoring).\n *\n * Loop safety: writes only ever touch the score fields (`leadScore`,\n * `leadScoreSummary`, `leadScoredAt`, `leadScoreStatus`). No update trigger\n * watches those fields, so a score write can never re-trigger scoring.\n */\nimport { CoreApiClient } from 'twenty-client-sdk/core';\nimport { runAgent } from 'twenty-sdk/logic-function';\n\nimport { LEAD_SCORER_AGENT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';\nimport {\n  buildOpportunityScoringPrompt,\n  buildPersonScoringPrompt,\n  type OpportunityScoringRecord,\n  type PersonScoringRecord,\n} from 'src/lib/build-prompt';\nimport { enrichRecord } from 'src/lib/enrich';\n\nexport type ScoreObjectType = 'person' | 'opportunity';\nexport type LeadScoreStatus = 'QUEUED' | 'SCORED' | 'SKIPPED' | 'FAILED';\n\ntype LeadScoreData = {\n  leadScoreStatus: LeadScoreStatus;\n  leadScore?: number;\n  leadScoreSummary?: string;\n  leadScoredAt?: string;\n};\n\nconst writeLeadScoreFields = async (\n  objectType: ScoreObjectType,\n  recordId: string,\n  data: LeadScoreData,\n): Promise<void> => {\n  const client = new CoreApiClient();\n\n  if (objectType === 'person') {\n    await client.mutation({\n      updatePerson: { __args: { id: recordId, data }, id: true },\n    });\n  } else {\n    await client.mutation({\n      updateOpportunity: { __args: { id: recordId, data }, id: true },\n    });\n  }\n};\n\n/** Write only the lifecycle status (used for SKIPPED / QUEUED / FAILED). */\nexport const setLeadScoreStatus = (\n  objectType: ScoreObjectType,\n  recordId: string,\n  status: LeadScoreStatus,\n): Promise<void> =>\n  writeLeadScoreFields(objectType, recordId, { leadScoreStatus: status });\n\nconst buildPromptFor = (\n  objectType: ScoreObjectType,\n  record: PersonScoringRecord | OpportunityScoringRecord,\n): string =>\n  objectType === 'person'\n    ? buildPersonScoringPrompt(record as PersonScoringRecord)\n    : buildOpportunityScoringPrompt(record as OpportunityScoringRecord);\n\n/**\n * Transient credit/quota exhaustion \u2014 retry when credits return, never fail\n * terminally. Matches the message the SDK throws on quota errors, e.g.\n * `Error(\"runAgent() failed: Credits exhausted\")`; string-matching is the only\n * signal available since `RunAgentResult` exposes only `error: string | null`.\n */\nconst CREDIT_EXHAUSTION_PATTERN = /credits?\\s+exhausted/i;\n\nconst reasonFrom = (source: unknown): string => {\n  const message =\n    source instanceof Error ? source.message : String(source ?? '');\n\n  return message.trim() || 'Unknown scoring error';\n};\n\n/**\n * Persist a non-success outcome without swallowing it (TCA-13). Credit\n * exhaustion is transient, so the record goes back to QUEUED to be re-scored\n * once credits return; any other error is terminal FAILED with the reason\n * captured in `leadScoreSummary` (and logged) so a failed record is\n * self-explaining.\n */\nconst recordFailure = async (\n  objectType: ScoreObjectType,\n  recordId: string,\n  source: unknown,\n): Promise<LeadScoreStatus> => {\n  const reason = reasonFrom(source);\n  console.error(`scoreRecord ${objectType} ${recordId} failed: ${reason}`);\n\n  if (CREDIT_EXHAUSTION_PATTERN.test(reason)) {\n    await setLeadScoreStatus(objectType, recordId, 'QUEUED');\n    return 'QUEUED';\n  }\n\n  await writeLeadScoreFields(objectType, recordId, {\n    leadScoreStatus: 'FAILED',\n    leadScoreSummary: reason,\n  });\n\n  return 'FAILED';\n};\n\n/**\n * Enrich, score via the agent, and persist the result.\n * Writes SCORED (with score + summary + timestamp) on success. On failure the\n * outcome is captured (never swallowed): credit exhaustion re-queues as QUEUED,\n * any other error is terminal FAILED with the reason in `leadScoreSummary`.\n * Returns the resulting status.\n */\nexport const scoreRecord = async (\n  objectType: ScoreObjectType,\n  recordId: string,\n): Promise<LeadScoreStatus> => {\n  try {\n    const record = await enrichRecord({ objectType, recordId });\n    const prompt = buildPromptFor(objectType, record);\n\n    const { result, error, success } = await runAgent({\n      agentUniversalIdentifier: LEAD_SCORER_AGENT_UNIVERSAL_IDENTIFIER,\n      prompt,\n    });\n\n    const parsed = result as { score?: number; summary?: string } | null;\n\n    if (!success || !parsed || typeof parsed.score !== 'number') {\n      return recordFailure(objectType, recordId, error ?? 'Agent returned no score');\n    }\n\n    await writeLeadScoreFields(objectType, recordId, {\n      leadScore: parsed.score,\n      leadScoreSummary: parsed.summary ?? '',\n      leadScoredAt: new Date().toISOString(),\n      leadScoreStatus: 'SCORED',\n    });\n\n    return 'SCORED';\n  } catch (error) {\n    return recordFailure(objectType, recordId, error);\n  }\n};\n", "/**\n * Enrichment helper (TCA-7).\n *\n * Fetches the scoring context for a single CRM record via the Core GraphQL API\n * (genql `CoreApiClient`) and normalizes it into the plain record shape that\n * `build-prompt.ts` consumes. Alongside the record's own scoring-relevant\n * fields it pulls the linked company and the most recent timeline activities so\n * the lead-scorer agent has richer signal than the bare record provides.\n *\n * Field names and selection shapes are taken from the generated core schema\n * (`twenty-client-sdk/dist/core/generated/schema.ts`): Person.name is a\n * `FullName` (firstName/lastName), Person.emails is `Emails` (primaryEmail),\n * Opportunity.amount is a `Currency` (amountMicros/currencyCode), Company has\n * `domainName` (Links) and `annualRevenue` (Currency), and both objects expose\n * a `timelineActivities` connection ordered by `createdAt`.\n */\nimport { CoreApiClient } from 'twenty-client-sdk/core';\n\nimport {\n  type OpportunityScoringRecord,\n  type PersonScoringRecord,\n} from 'src/lib/build-prompt';\n\nexport type EnrichmentInput = {\n  objectType: 'person' | 'opportunity';\n  recordId: string;\n};\n\nconst RECENT_ACTIVITY_LIMIT = 10;\n\ntype TimelineActivitiesResult = {\n  edges?:\n    | ({ node?: { name?: string | null } | null } | null)[]\n    | null;\n};\n\nconst nonEmpty = (value: string | null | undefined): value is string =>\n  typeof value === 'string' && value.trim() !== '';\n\nconst joinName = (\n  name: { firstName?: string | null; lastName?: string | null } | null | undefined,\n): string | undefined => {\n  const joined = [name?.firstName, name?.lastName].filter(nonEmpty).join(' ');\n\n  return joined === '' ? undefined : joined;\n};\n\nconst recentActivity = (\n  timelineActivities: TimelineActivitiesResult | null | undefined,\n): string[] | undefined => {\n  const names = (timelineActivities?.edges ?? [])\n    .map((edge) => edge?.node?.name)\n    .filter(nonEmpty);\n\n  return names.length > 0 ? names : undefined;\n};\n\n/** Convert a `Currency.amountMicros` (micros, 1e6 = one unit) to a plain number. */\nconst microsToAmount = (\n  amountMicros: string | number | null | undefined,\n): number | undefined => {\n  if (amountMicros === null || amountMicros === undefined) {\n    return undefined;\n  }\n\n  const asNumber = Number(amountMicros);\n\n  return Number.isFinite(asNumber) ? asNumber / 1_000_000 : undefined;\n};\n\nconst enrichPerson = async (\n  client: CoreApiClient,\n  recordId: string,\n): Promise<PersonScoringRecord> => {\n  const { person } = await client.query({\n    person: {\n      __args: { filter: { id: { eq: recordId } } },\n      id: true,\n      name: { firstName: true, lastName: true },\n      jobTitle: true,\n      emails: { primaryEmail: true },\n      linkedinLink: { primaryLinkUrl: true },\n      leadScore: true,\n      company: {\n        name: true,\n        domainName: { primaryLinkUrl: true },\n        annualRevenue: { amountMicros: true, currencyCode: true },\n        address: { addressCity: true, addressState: true, addressCountry: true },\n      },\n      timelineActivities: {\n        __args: {\n          first: RECENT_ACTIVITY_LIMIT,\n          orderBy: [{ createdAt: 'DescNullsLast' }],\n        },\n        edges: {\n          node: { id: true, name: true, happensAt: true, createdAt: true },\n        },\n      },\n    },\n  });\n\n  if (!person) {\n    throw new Error(`Person not found for id ${recordId}`);\n  }\n\n  return {\n    name: joinName(person.name),\n    jobTitle: person.jobTitle ?? undefined,\n    company: person.company?.name ?? undefined,\n    companyDomain: person.company?.domainName?.primaryLinkUrl ?? undefined,\n    city: person.company?.address?.addressCity ?? undefined,\n    email: person.emails?.primaryEmail ?? undefined,\n    linkedinUrl: person.linkedinLink?.primaryLinkUrl ?? undefined,\n    recentActivity: recentActivity(person.timelineActivities),\n  };\n};\n\nconst enrichOpportunity = async (\n  client: CoreApiClient,\n  recordId: string,\n): Promise<OpportunityScoringRecord> => {\n  const { opportunity } = await client.query({\n    opportunity: {\n      __args: { filter: { id: { eq: recordId } } },\n      id: true,\n      name: true,\n      amount: { amountMicros: true, currencyCode: true },\n      stage: true,\n      closeDate: true,\n      leadScore: true,\n      company: {\n        name: true,\n        domainName: { primaryLinkUrl: true },\n        annualRevenue: { amountMicros: true, currencyCode: true },\n        address: { addressCity: true, addressState: true, addressCountry: true },\n      },\n      timelineActivities: {\n        __args: {\n          first: RECENT_ACTIVITY_LIMIT,\n          orderBy: [{ createdAt: 'DescNullsLast' }],\n        },\n        edges: {\n          node: { id: true, name: true, happensAt: true, createdAt: true },\n        },\n      },\n    },\n  });\n\n  if (!opportunity) {\n    throw new Error(`Opportunity not found for id ${recordId}`);\n  }\n\n  return {\n    name: opportunity.name ?? undefined,\n    amount: microsToAmount(opportunity.amount?.amountMicros),\n    stage: opportunity.stage ?? undefined,\n    closeDate: opportunity.closeDate ?? undefined,\n    company: opportunity.company?.name ?? undefined,\n    companyDomain: opportunity.company?.domainName?.primaryLinkUrl ?? undefined,\n    recentActivity: recentActivity(opportunity.timelineActivities),\n  };\n};\n\n/**\n * Fetch and normalize the scoring context for a single person or opportunity.\n * The returned object is shaped for `buildPersonScoringPrompt` /\n * `buildOpportunityScoringPrompt`.\n */\nexport const enrichRecord = async (\n  input: EnrichmentInput,\n): Promise<PersonScoringRecord | OpportunityScoringRecord> => {\n  const client = new CoreApiClient();\n\n  return input.objectType === 'person'\n    ? enrichPerson(client, input.recordId)\n    : enrichOpportunity(client, input.recordId);\n};\n", "import { defineLogicFunction, HTTPMethod } from 'twenty-sdk/define';\n\nimport { BACKFILL_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';\nimport { backfillUnscored } from 'src/lib/backfill';\n\nexport default defineLogicFunction({\n  universalIdentifier: BACKFILL_FUNCTION_UNIVERSAL_IDENTIFIER,\n  name: 'backfill',\n  description:\n    'Enqueue pre-existing unscored people + opportunities so the drainQueue cron scores them. Idempotent.',\n  timeoutSeconds: 60,\n  httpRouteTriggerSettings: {\n    path: '/backfill',\n    httpMethod: HTTPMethod.POST,\n    isAuthRequired: true,\n  },\n  handler: async () => {\n    const result = await backfillUnscored();\n\n    return { queued: result };\n  },\n});\n"],
  "mappings": "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",
  "names": ["CoreApiClient", "CoreApiClient", "CoreApiClient", "CoreApiClient"]
}
