@singi-labs/sifa-sdk 0.19.23 → 0.19.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6637,7 +6637,7 @@ function describeSifaRecord(collection, value) {
6637
6637
  }
6638
6638
 
6639
6639
  // src/index.ts
6640
- var SIFA_SDK_VERSION = "0.19.23";
6640
+ var SIFA_SDK_VERSION = "0.19.24";
6641
6641
 
6642
6642
  exports.ACTIVITY_TIERS = ACTIVITY_TIERS;
6643
6643
  exports.ACTIVITY_VERBS = ACTIVITY_VERBS;
package/dist/index.js CHANGED
@@ -6635,7 +6635,7 @@ function describeSifaRecord(collection, value) {
6635
6635
  }
6636
6636
 
6637
6637
  // src/index.ts
6638
- var SIFA_SDK_VERSION = "0.19.23";
6638
+ var SIFA_SDK_VERSION = "0.19.24";
6639
6639
 
6640
6640
  export { ACTIVITY_TIERS, ACTIVITY_VERBS, ACTIVITY_VISIBILITY_RULES, ADULT_CONTENT_LABELS, ALL_SECTIONS, APP_CATEGORIES, APP_CATEGORY_IDS, APP_CATEGORY_MAP, APP_URL_PATTERNS, ARTIFACT_LINK_KIND_LABELS, ARTIFACT_LINK_KIND_OPTIONS, ArtifactLinkSchema, AtmosphereFeedItemSchema, BlobRefSchema, CALENDAR_EVENT_MODE_LABELS, CALENDAR_EVENT_STATUS_LABELS, CATEGORY_LABELS, CATEGORY_ORDER, COLLECTION_TO_APP, COMPANY_OPTIONAL_EMPLOYMENT_TYPES, COMPANY_PAGE_MIN_FIRMOGRAPHIC_FIELDS, COMPLETENESS_MAX_SCORE, CONTINENTS, COUNTRIES, DIMENSIONS_MAX_SCORE, EMPLOYMENT_TYPE_GROUPS, EMPLOYMENT_TYPE_LABELS, ENTITY_REF_ANCHORS, EndorsementConfirmationRecordSchema, EndorsementRecordSchema, EntityImportSearchResponseSchema, EntityMintDomainResponseSchema, EntityResolveDomainRequestSchema, EntityResolveDomainResponseSchema, EntitySearchResponseSchema, EntitySearchResultSchema, EntitySelectRequestSchema, EntitySelectResponseSchema, FEATURE_FLAGS, FeatureAllowlistEntrySchema, FeedActorSchema, FollowFeedItemSchema, FollowFeedPageSchema, FollowProfilePageSchema, FollowProfileSchema, GraphFollowRecordSchema, INDUSTRY_OPTIONS, INVESTMENT_ROLE_LABELS, INVESTMENT_ROLE_OPTIONS, INVESTMENT_STAGE_LABELS, INVESTMENT_STAGE_OPTIONS, INVESTMENT_STATUS_LABELS, INVESTMENT_STATUS_OPTIONS, INVOLVEMENT_HEADING_ORDER, INVOLVEMENT_KIND_HEADINGS, INVOLVEMENT_KIND_LABELS, INVOLVEMENT_KIND_OPTIONS, InvestmentAmountSchema, MIN_SKILLS, ON_BEHALF_OF_EMPLOYMENT_TYPES, OPEN_TO_OPTIONS, OPEN_TO_TOKENS, OPEN_TO_TOKEN_TO_VALUE, OPEN_TO_VALUE_TO_TOKEN, OrgEmploymentAttestationRecordSchema, OrgProfileRecordSchema, PLATFORM_LABELS, PLATFORM_OPTIONS, PRESENTATION_LINK_TYPE_LABELS, PRESENTATION_LINK_TYPE_OPTIONS, PRESENTATION_ROLE_LABELS, PRESENTATION_ROLE_OPTIONS, PROFILE_INVESTMENT_NSID, PROFILE_INVOLVEMENT_NSID, PROJECT_ROLES, PUBLICATION_TYPE_LABELS, PUBLICATION_TYPE_OPTIONS, PUBLISHERS, PresentationDurationSchema, PresentationLinkSchema, ProfileCertificationRecordSchema, ProfileCourseRecordSchema, ProfileEducationRecordSchema, ProfileExternalAccountRecordSchema, ProfileHonorRecordSchema, ProfileInvestmentRecordSchema, ProfileInvolvementRecordSchema, ProfileLanguageRecordSchema, ProfilePositionRecordSchema, ProfilePresentationDeliveryRecordSchema, ProfilePresentationRecordSchema, ProfileProjectRecordSchema, ProfilePublicationRecordSchema, ProfileSelfRecordSchema, ProfileSkillRecordSchema, ProfileVolunteeringRecordSchema, PublicationAuthorSchema, ROADMAP_ISSUES_REPO, ROADMAP_ITEM_META, SECTION_GROUPS, SECTION_LABELS, SELF_APP_ID, SIFA_REPO_GROUPS, SIFA_SDK_VERSION, SKILL_CATEGORIES, STANDARD_PUBLISHER_ID, STREAM_VERBS, SifaFeedItemSchema, VERIFICATION_PROVIDERS, WORKPLACE_TYPE_LABELS, WORKPLACE_TYPE_OPTIONS, actorShowsIdentity, agentRefSchema, atUriSchema, buildMetaDescription, buildProfileHighlights, buildTalkSlug, categoryForApp, certDateExtractor, cidSchema, classifyEntityRef, completenessPercent, completenessScore, contrastRatio, countFilledDimensions, countRecentActivity, countryCodeToFlag, dateRangeExtractor, datetimeSchema, decodeFeedCursor, dedupeSkills, describeSifaRecord, detectPdsProvider, didSchema, dimensionsFromInputs, durationFromMinutes, encodeFeedCursor, entityDisambiguationLabel, entityResultKey, excludeSelfApp, externalRecordRefSchema, filterHidden, findIndustry, formatCompanyName, formatDateRange, formatDisplayUrl, formatDistanceToNow, formatEventDate, formatLocation, formatPresentationDuration, formatRelationship, formatRelativeTime, formatSingleDate, formatSpanDate, formatTimelineDate, getActivityTaxonomyVersion, getActivityTier, getActivityVerbsVersion, getAppCategoryIcon, getAppIdForCollection, getArtifactLinkKindLabel, getCalendarEventModeLabel, getCalendarEventStatusLabel, getContinent, getDisplayLabel, getEmploymentTypeLabel, getFaviconUrl, getFilledDimensionsMap, getHandleStem, getIndustryLabelKey, getInvestmentRoleLabel, getInvestmentStageLabel, getInvestmentStatusLabel, getInvolvementKindHeading, getInvolvementKindLabel, getLexiconEntry, getOpenToLabelKey, getPdsDisplayName, getPlatformLabel, getPresentationLinkTypeLabel, getPresentationRoleLabel, getPublicationTypeLabel, getPublisherByHost, getPublisherById, getPublisherFromSiteUrl, getTierMeta, getVerificationProvider, getVisibleSectionIds, getWorkplaceTypeLabel, groupInvolvementByHeading, groupSkillsByCategory, groupSkillsBySubCategory, groupSkillsForDisplay, hasAdultContent, hasPersonalProfileContent, hoistPrimary, isAppCategory, isCompanyPageIndexable, isCompanyRequired, isKnownAppId, isKnownPlatform, isKnownVerificationProvider, isLinked, isOnBehalfOfApplicable, isPseudoEmployer, isRegistrableDomainHandle, isRelationalActivity, isRoleLineRedundant, isSectionPopulated, isSelfAuthoredRich, isValidRgbColor, isVisibleActivityItem, languageTagSchema, lexiconDateExtractor, limitCombiningMarks, locationSegments, looksLikeDomain, makeGraphFollowRecordSchema, maxGraphemes, meetsContrastAA, normalizeCompanyKey, normalizeDoi, normalizeLegalForm, normalizeOpenTo, normalizePlatformId, normalizePresentationMode, normalizePresentationRole, normalizeSkillCategory, normalizeWorkplaceTypes, openToTokenToValue, openToValueToToken, parseEndorsementComment, parseIntendedAudiences, parseLocationString, parsePresentationDuration, parseTalkRkey, partialDateSchema, pdsProviderFromApi, pickPrimaryFlagged, pickPrimaryPosition, presentationCsvRowToRecord, presentationDeliveryCsvRowToRecord, primaryVerification, profileToDimensionInputs, qualifiesAsOrg, relativeLuminance, renderAsLine, rendersCompanyProfile, rendersPersonalProfile, repoGroupForCollection, resolveAccountFacetMode, resolveAgentRef, resolveCardHealth, resolveCardUrl, resolveEmbed, resolveVerifierProvider, rgbToString, roadmapIssueUrl, sanitizeDisplayText, sanitizeHandleInput, searchResultDisambiguation, selfLabelsSchema, shouldRenderHighlights, singleDateExtractor, skillRefSchema, slugifyTitle, sortByActiveDateRange, sortByDateDesc, sortCertifications, sortEducation, sortHonors, sortLanguages, sortLanguagesByProficiency, sortPositions, sortProjects, sortPublications, streamAddressSchema, streamAuthorSchema, streamCardBodySchema, streamCardSubjectSchema, streamCardVMSchema, streamExternalLinkSchema, streamGeoSchema, streamMediaSchema, streamRichSegmentSchema, streamSourceSchema, streamThemeSchema, streamVerbSchema, stripHtmlToText, strongRefSchema, summarizePresentationDeliveries, summarizeProfileView, toStreamCardVM, toStreamCardVMs, truncateGraphemes, uriSchema, verbForCollection, visibleItems, youtubeThumbnailUrl, youtubeVideoId };
6641
6641
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ // src/jev/index.ts
6
+ var noulAnswerSchema = zod.z.object({
7
+ type: zod.z.literal("noul"),
8
+ noul: zod.z.number().min(0).max(1)
9
+ });
10
+ var choiceAnswerSchema = zod.z.object({
11
+ type: zod.z.literal("choice"),
12
+ choice: zod.z.string(),
13
+ confidence: zod.z.number().min(0).max(1),
14
+ probabilities: zod.z.record(zod.z.string(), zod.z.number())
15
+ });
16
+ function compact(obj) {
17
+ const out = {};
18
+ for (const [k, v] of Object.entries(obj)) {
19
+ if (v !== void 0 && v !== null && v !== "") out[k] = v;
20
+ }
21
+ return out;
22
+ }
23
+ function buildOrgPairState(a, b) {
24
+ return { orgA: compact(a), orgB: compact(b) };
25
+ }
26
+ var ORG_PAIR_NOUL_QUESTION = {
27
+ type: "noul",
28
+ instructions: "The state holds two organisation records, orgA and orgB. Do they refer to the same real-world organisation?",
29
+ criteria: {
30
+ true: "Same organisation, e.g. a legal-suffix or abbreviation variant of the same name, or the same primary domain.",
31
+ false: "Distinct organisations, e.g. a shared word in the name but different domains, locations, or lines of business."
32
+ }
33
+ };
34
+ var SAME_ORG_THRESHOLD = 0.85;
35
+ function isLikelySameOrg(noul, threshold = SAME_ORG_THRESHOLD) {
36
+ return noul >= threshold;
37
+ }
38
+ var INDUSTRY_TAXONOMY = {
39
+ technology: "Software, IT services, internet, hardware, telecoms",
40
+ finance: "Banking, insurance, investment, fintech, accounting",
41
+ healthcare: "Medical care, pharma, biotech, health services",
42
+ education: "Schools, universities, training, edtech",
43
+ manufacturing: "Industrial production, hardware, materials, automotive",
44
+ retail: "Retail, ecommerce, consumer goods, hospitality, food",
45
+ media: "Media, publishing, entertainment, marketing, advertising",
46
+ energy: "Energy, utilities, oil and gas, renewables",
47
+ construction: "Construction, real estate, architecture, engineering",
48
+ transport: "Transport, logistics, shipping, aviation",
49
+ professional_services: "Legal, consulting, HR, business services",
50
+ public_sector: "Government, defence, public administration",
51
+ nonprofit: "Charities, NGOs, foundations, associations",
52
+ agriculture: "Agriculture, farming, fishing, forestry",
53
+ other: "None of the above, or not enough information to decide"
54
+ };
55
+ var ORG_TYPE_TAXONOMY = {
56
+ company: "For-profit company or corporation",
57
+ nonprofit: "Nonprofit, charity, NGO, or foundation",
58
+ government: "Government body or public-sector entity",
59
+ educational: "School, university, or research institution",
60
+ agency: "Agency, consultancy, or professional-services firm",
61
+ other: "None of the above, or not enough information to decide"
62
+ };
63
+ var SIZE_BAND_TAXONOMY = {
64
+ solo: "1 person",
65
+ small: "2 to 10 people",
66
+ medium: "11 to 50 people",
67
+ large: "51 to 250 people",
68
+ enterprise: "More than 250 people",
69
+ unknown: "Not enough information to decide"
70
+ };
71
+ function choiceQuestion(instructions, criteria) {
72
+ return { type: "choice", instructions, criteria };
73
+ }
74
+ var INDUSTRY_CHOICE_QUESTION = choiceQuestion(
75
+ "Based on the org state, which single industry best describes this organisation?",
76
+ INDUSTRY_TAXONOMY
77
+ );
78
+ var ORG_TYPE_CHOICE_QUESTION = choiceQuestion(
79
+ "Based on the org state, which single type best describes this organisation?",
80
+ ORG_TYPE_TAXONOMY
81
+ );
82
+ var SIZE_BAND_CHOICE_QUESTION = choiceQuestion(
83
+ "Based on the org state, which employee-size band best fits this organisation?",
84
+ SIZE_BAND_TAXONOMY
85
+ );
86
+ function buildFirmographicState(org) {
87
+ return compact(org);
88
+ }
89
+ var FIRMOGRAPHIC_CONFIDENCE_FLOOR = 0.6;
90
+ function pickChoiceAboveFloor(answer, opts = {}) {
91
+ const floor = opts.floor ?? FIRMOGRAPHIC_CONFIDENCE_FLOOR;
92
+ if (answer.confidence < floor) return null;
93
+ if (opts.noMatch !== void 0 && answer.choice === opts.noMatch) return null;
94
+ return answer.choice;
95
+ }
96
+
97
+ exports.FIRMOGRAPHIC_CONFIDENCE_FLOOR = FIRMOGRAPHIC_CONFIDENCE_FLOOR;
98
+ exports.INDUSTRY_CHOICE_QUESTION = INDUSTRY_CHOICE_QUESTION;
99
+ exports.INDUSTRY_TAXONOMY = INDUSTRY_TAXONOMY;
100
+ exports.ORG_PAIR_NOUL_QUESTION = ORG_PAIR_NOUL_QUESTION;
101
+ exports.ORG_TYPE_CHOICE_QUESTION = ORG_TYPE_CHOICE_QUESTION;
102
+ exports.ORG_TYPE_TAXONOMY = ORG_TYPE_TAXONOMY;
103
+ exports.SAME_ORG_THRESHOLD = SAME_ORG_THRESHOLD;
104
+ exports.SIZE_BAND_CHOICE_QUESTION = SIZE_BAND_CHOICE_QUESTION;
105
+ exports.SIZE_BAND_TAXONOMY = SIZE_BAND_TAXONOMY;
106
+ exports.buildFirmographicState = buildFirmographicState;
107
+ exports.buildOrgPairState = buildOrgPairState;
108
+ exports.choiceAnswerSchema = choiceAnswerSchema;
109
+ exports.isLikelySameOrg = isLikelySameOrg;
110
+ exports.noulAnswerSchema = noulAnswerSchema;
111
+ exports.pickChoiceAboveFloor = pickChoiceAboveFloor;
112
+ //# sourceMappingURL=index.cjs.map
113
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/jev/index.ts"],"names":["z"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * `@singi-labs/sifa-sdk/jev` -- TypeSafe System One (Jev) question builders,\n * taxonomies, result schemas, and threshold predicates for Sifa's organisation\n * intelligence.\n *\n * This subpath is pure: it defines the typed *questions* to ask Jev, validates\n * the *answers*, and holds the policy constants (thresholds, taxonomies) that\n * turn a raw judgment into a decision. It performs no network calls and needs\n * no API key, so it is safe to import anywhere. The actual `systemOne` call\n * lives server-side in sifa-api, which passes an API key that must never reach\n * the browser.\n *\n * Judgment shapes:\n * - Duplicate-org detection: a single Noul over a candidate pair.\n * - Firmographic classification: independent Choice questions (industry, type,\n * size) asked together over one org's state.\n *\n * See docs.typesafe.ai for the wire contract. Typed output guarantees the\n * interface, not truth: validate thresholds on labelled Sifa data before\n * enabling any write.\n */\n\nimport { z } from 'zod';\n\n// ---------------------------------------------------------------------------\n// Wire question types\n// ---------------------------------------------------------------------------\n\n/** A yes/no judgment. `criteria` clarifies what true vs false means. */\nexport interface NoulQuestion {\n type: 'noul';\n instructions: string;\n criteria?: { true: string; false: string };\n}\n\n/** A single-selection judgment. `criteria` maps each option to its description. */\nexport interface ChoiceQuestion {\n type: 'choice';\n instructions: string;\n criteria: Record<string, string>;\n}\n\n// ---------------------------------------------------------------------------\n// Answer schemas (validate Jev's response before code trusts it)\n// ---------------------------------------------------------------------------\n\n/** Jev's Noul answer: `noul` is the probability that the answer is yes (0..1). */\nexport const noulAnswerSchema = z.object({\n type: z.literal('noul'),\n noul: z.number().min(0).max(1),\n});\nexport type NoulAnswer = z.infer<typeof noulAnswerSchema>;\n\n/**\n * Jev's Choice answer: `choice` is the highest-probability option, `confidence`\n * summarises how concentrated the distribution is, `probabilities` sums to 1.\n */\nexport const choiceAnswerSchema = z.object({\n type: z.literal('choice'),\n choice: z.string(),\n confidence: z.number().min(0).max(1),\n probabilities: z.record(z.string(), z.number()),\n});\nexport type ChoiceAnswer = z.infer<typeof choiceAnswerSchema>;\n\n// ---------------------------------------------------------------------------\n// Duplicate-org detection (Noul) -- ticket #560\n// ---------------------------------------------------------------------------\n\n/** Public, non-user fields we compare when judging whether two orgs are the same. */\nexport interface OrgRecordInput {\n name: string;\n domain?: string;\n location?: string;\n description?: string;\n}\n\n/** Build a plain object with only the defined keys (Jev sees no `undefined`). */\nfunction compact<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined && v !== null && v !== '') out[k as keyof T] = v as T[keyof T];\n }\n return out;\n}\n\n/**\n * State for the org-pair judgment. Both records live under stable named fields\n * so the question can reference them unambiguously.\n */\nexport function buildOrgPairState(\n a: OrgRecordInput,\n b: OrgRecordInput,\n): { orgA: Partial<OrgRecordInput>; orgB: Partial<OrgRecordInput> } {\n return { orgA: compact(a), orgB: compact(b) };\n}\n\n/** Static Noul question paired with {@link buildOrgPairState}. */\nexport const ORG_PAIR_NOUL_QUESTION: NoulQuestion = {\n type: 'noul',\n instructions:\n 'The state holds two organisation records, orgA and orgB. Do they refer to the same real-world organisation?',\n criteria: {\n true: 'Same organisation, e.g. a legal-suffix or abbreviation variant of the same name, or the same primary domain.',\n false:\n 'Distinct organisations, e.g. a shared word in the name but different domains, locations, or lines of business.',\n },\n};\n\n/**\n * Threshold favouring precision: a wrong merge is worse than a missed one.\n * Validate on labelled pairs (ticket #560) before enabling merges in prod.\n */\nexport const SAME_ORG_THRESHOLD = 0.85;\n\n/** True when the Noul probability clears the merge threshold. */\nexport function isLikelySameOrg(noul: number, threshold: number = SAME_ORG_THRESHOLD): boolean {\n return noul >= threshold;\n}\n\n// ---------------------------------------------------------------------------\n// Firmographic classification (Choice) -- ticket #561\n// ---------------------------------------------------------------------------\n\n/** Industry taxonomy. Single source of truth; `other` is the no-match option. */\nexport const INDUSTRY_TAXONOMY: Record<string, string> = {\n technology: 'Software, IT services, internet, hardware, telecoms',\n finance: 'Banking, insurance, investment, fintech, accounting',\n healthcare: 'Medical care, pharma, biotech, health services',\n education: 'Schools, universities, training, edtech',\n manufacturing: 'Industrial production, hardware, materials, automotive',\n retail: 'Retail, ecommerce, consumer goods, hospitality, food',\n media: 'Media, publishing, entertainment, marketing, advertising',\n energy: 'Energy, utilities, oil and gas, renewables',\n construction: 'Construction, real estate, architecture, engineering',\n transport: 'Transport, logistics, shipping, aviation',\n professional_services: 'Legal, consulting, HR, business services',\n public_sector: 'Government, defence, public administration',\n nonprofit: 'Charities, NGOs, foundations, associations',\n agriculture: 'Agriculture, farming, fishing, forestry',\n other: 'None of the above, or not enough information to decide',\n};\n\n/** Organisation type taxonomy; `other` is the no-match option. */\nexport const ORG_TYPE_TAXONOMY: Record<string, string> = {\n company: 'For-profit company or corporation',\n nonprofit: 'Nonprofit, charity, NGO, or foundation',\n government: 'Government body or public-sector entity',\n educational: 'School, university, or research institution',\n agency: 'Agency, consultancy, or professional-services firm',\n other: 'None of the above, or not enough information to decide',\n};\n\n/** Ordered employee-size bands; `unknown` is the no-match option. */\nexport const SIZE_BAND_TAXONOMY: Record<string, string> = {\n solo: '1 person',\n small: '2 to 10 people',\n medium: '11 to 50 people',\n large: '51 to 250 people',\n enterprise: 'More than 250 people',\n unknown: 'Not enough information to decide',\n};\n\nfunction choiceQuestion(instructions: string, criteria: Record<string, string>): ChoiceQuestion {\n return { type: 'choice', instructions, criteria };\n}\n\nexport const INDUSTRY_CHOICE_QUESTION = choiceQuestion(\n 'Based on the org state, which single industry best describes this organisation?',\n INDUSTRY_TAXONOMY,\n);\n\nexport const ORG_TYPE_CHOICE_QUESTION = choiceQuestion(\n 'Based on the org state, which single type best describes this organisation?',\n ORG_TYPE_TAXONOMY,\n);\n\nexport const SIZE_BAND_CHOICE_QUESTION = choiceQuestion(\n 'Based on the org state, which employee-size band best fits this organisation?',\n SIZE_BAND_TAXONOMY,\n);\n\n/** Input for firmographic classification. Enrichment text is the primary evidence. */\nexport interface FirmographicInput {\n name: string;\n domain?: string;\n enrichmentText: string;\n}\n\n/** State for the firmographic questions. */\nexport function buildFirmographicState(org: FirmographicInput): Partial<FirmographicInput> {\n return compact(org);\n}\n\n/**\n * Below this confidence a firmographic field is left unset rather than guessed.\n * Validate per field on labelled orgs (ticket #561).\n */\nexport const FIRMOGRAPHIC_CONFIDENCE_FLOOR = 0.6;\n\n/**\n * Select the classified option, or `null` to leave the field unset. Returns\n * null when confidence is below the floor, or when the selected option is the\n * taxonomy's no-match option.\n */\nexport function pickChoiceAboveFloor(\n answer: ChoiceAnswer,\n opts: { floor?: number; noMatch?: string } = {},\n): string | null {\n const floor = opts.floor ?? FIRMOGRAPHIC_CONFIDENCE_FLOOR;\n if (answer.confidence < floor) return null;\n if (opts.noMatch !== undefined && answer.choice === opts.noMatch) return null;\n return answer.choice;\n}\n"]}
@@ -0,0 +1,113 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * `@singi-labs/sifa-sdk/jev` -- TypeSafe System One (Jev) question builders,
5
+ * taxonomies, result schemas, and threshold predicates for Sifa's organisation
6
+ * intelligence.
7
+ *
8
+ * This subpath is pure: it defines the typed *questions* to ask Jev, validates
9
+ * the *answers*, and holds the policy constants (thresholds, taxonomies) that
10
+ * turn a raw judgment into a decision. It performs no network calls and needs
11
+ * no API key, so it is safe to import anywhere. The actual `systemOne` call
12
+ * lives server-side in sifa-api, which passes an API key that must never reach
13
+ * the browser.
14
+ *
15
+ * Judgment shapes:
16
+ * - Duplicate-org detection: a single Noul over a candidate pair.
17
+ * - Firmographic classification: independent Choice questions (industry, type,
18
+ * size) asked together over one org's state.
19
+ *
20
+ * See docs.typesafe.ai for the wire contract. Typed output guarantees the
21
+ * interface, not truth: validate thresholds on labelled Sifa data before
22
+ * enabling any write.
23
+ */
24
+
25
+ /** A yes/no judgment. `criteria` clarifies what true vs false means. */
26
+ interface NoulQuestion {
27
+ type: 'noul';
28
+ instructions: string;
29
+ criteria?: {
30
+ true: string;
31
+ false: string;
32
+ };
33
+ }
34
+ /** A single-selection judgment. `criteria` maps each option to its description. */
35
+ interface ChoiceQuestion {
36
+ type: 'choice';
37
+ instructions: string;
38
+ criteria: Record<string, string>;
39
+ }
40
+ /** Jev's Noul answer: `noul` is the probability that the answer is yes (0..1). */
41
+ declare const noulAnswerSchema: z.ZodObject<{
42
+ type: z.ZodLiteral<"noul">;
43
+ noul: z.ZodNumber;
44
+ }, z.core.$strip>;
45
+ type NoulAnswer = z.infer<typeof noulAnswerSchema>;
46
+ /**
47
+ * Jev's Choice answer: `choice` is the highest-probability option, `confidence`
48
+ * summarises how concentrated the distribution is, `probabilities` sums to 1.
49
+ */
50
+ declare const choiceAnswerSchema: z.ZodObject<{
51
+ type: z.ZodLiteral<"choice">;
52
+ choice: z.ZodString;
53
+ confidence: z.ZodNumber;
54
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
55
+ }, z.core.$strip>;
56
+ type ChoiceAnswer = z.infer<typeof choiceAnswerSchema>;
57
+ /** Public, non-user fields we compare when judging whether two orgs are the same. */
58
+ interface OrgRecordInput {
59
+ name: string;
60
+ domain?: string;
61
+ location?: string;
62
+ description?: string;
63
+ }
64
+ /**
65
+ * State for the org-pair judgment. Both records live under stable named fields
66
+ * so the question can reference them unambiguously.
67
+ */
68
+ declare function buildOrgPairState(a: OrgRecordInput, b: OrgRecordInput): {
69
+ orgA: Partial<OrgRecordInput>;
70
+ orgB: Partial<OrgRecordInput>;
71
+ };
72
+ /** Static Noul question paired with {@link buildOrgPairState}. */
73
+ declare const ORG_PAIR_NOUL_QUESTION: NoulQuestion;
74
+ /**
75
+ * Threshold favouring precision: a wrong merge is worse than a missed one.
76
+ * Validate on labelled pairs (ticket #560) before enabling merges in prod.
77
+ */
78
+ declare const SAME_ORG_THRESHOLD = 0.85;
79
+ /** True when the Noul probability clears the merge threshold. */
80
+ declare function isLikelySameOrg(noul: number, threshold?: number): boolean;
81
+ /** Industry taxonomy. Single source of truth; `other` is the no-match option. */
82
+ declare const INDUSTRY_TAXONOMY: Record<string, string>;
83
+ /** Organisation type taxonomy; `other` is the no-match option. */
84
+ declare const ORG_TYPE_TAXONOMY: Record<string, string>;
85
+ /** Ordered employee-size bands; `unknown` is the no-match option. */
86
+ declare const SIZE_BAND_TAXONOMY: Record<string, string>;
87
+ declare const INDUSTRY_CHOICE_QUESTION: ChoiceQuestion;
88
+ declare const ORG_TYPE_CHOICE_QUESTION: ChoiceQuestion;
89
+ declare const SIZE_BAND_CHOICE_QUESTION: ChoiceQuestion;
90
+ /** Input for firmographic classification. Enrichment text is the primary evidence. */
91
+ interface FirmographicInput {
92
+ name: string;
93
+ domain?: string;
94
+ enrichmentText: string;
95
+ }
96
+ /** State for the firmographic questions. */
97
+ declare function buildFirmographicState(org: FirmographicInput): Partial<FirmographicInput>;
98
+ /**
99
+ * Below this confidence a firmographic field is left unset rather than guessed.
100
+ * Validate per field on labelled orgs (ticket #561).
101
+ */
102
+ declare const FIRMOGRAPHIC_CONFIDENCE_FLOOR = 0.6;
103
+ /**
104
+ * Select the classified option, or `null` to leave the field unset. Returns
105
+ * null when confidence is below the floor, or when the selected option is the
106
+ * taxonomy's no-match option.
107
+ */
108
+ declare function pickChoiceAboveFloor(answer: ChoiceAnswer, opts?: {
109
+ floor?: number;
110
+ noMatch?: string;
111
+ }): string | null;
112
+
113
+ export { type ChoiceAnswer, type ChoiceQuestion, FIRMOGRAPHIC_CONFIDENCE_FLOOR, type FirmographicInput, INDUSTRY_CHOICE_QUESTION, INDUSTRY_TAXONOMY, type NoulAnswer, type NoulQuestion, ORG_PAIR_NOUL_QUESTION, ORG_TYPE_CHOICE_QUESTION, ORG_TYPE_TAXONOMY, type OrgRecordInput, SAME_ORG_THRESHOLD, SIZE_BAND_CHOICE_QUESTION, SIZE_BAND_TAXONOMY, buildFirmographicState, buildOrgPairState, choiceAnswerSchema, isLikelySameOrg, noulAnswerSchema, pickChoiceAboveFloor };
@@ -0,0 +1,113 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * `@singi-labs/sifa-sdk/jev` -- TypeSafe System One (Jev) question builders,
5
+ * taxonomies, result schemas, and threshold predicates for Sifa's organisation
6
+ * intelligence.
7
+ *
8
+ * This subpath is pure: it defines the typed *questions* to ask Jev, validates
9
+ * the *answers*, and holds the policy constants (thresholds, taxonomies) that
10
+ * turn a raw judgment into a decision. It performs no network calls and needs
11
+ * no API key, so it is safe to import anywhere. The actual `systemOne` call
12
+ * lives server-side in sifa-api, which passes an API key that must never reach
13
+ * the browser.
14
+ *
15
+ * Judgment shapes:
16
+ * - Duplicate-org detection: a single Noul over a candidate pair.
17
+ * - Firmographic classification: independent Choice questions (industry, type,
18
+ * size) asked together over one org's state.
19
+ *
20
+ * See docs.typesafe.ai for the wire contract. Typed output guarantees the
21
+ * interface, not truth: validate thresholds on labelled Sifa data before
22
+ * enabling any write.
23
+ */
24
+
25
+ /** A yes/no judgment. `criteria` clarifies what true vs false means. */
26
+ interface NoulQuestion {
27
+ type: 'noul';
28
+ instructions: string;
29
+ criteria?: {
30
+ true: string;
31
+ false: string;
32
+ };
33
+ }
34
+ /** A single-selection judgment. `criteria` maps each option to its description. */
35
+ interface ChoiceQuestion {
36
+ type: 'choice';
37
+ instructions: string;
38
+ criteria: Record<string, string>;
39
+ }
40
+ /** Jev's Noul answer: `noul` is the probability that the answer is yes (0..1). */
41
+ declare const noulAnswerSchema: z.ZodObject<{
42
+ type: z.ZodLiteral<"noul">;
43
+ noul: z.ZodNumber;
44
+ }, z.core.$strip>;
45
+ type NoulAnswer = z.infer<typeof noulAnswerSchema>;
46
+ /**
47
+ * Jev's Choice answer: `choice` is the highest-probability option, `confidence`
48
+ * summarises how concentrated the distribution is, `probabilities` sums to 1.
49
+ */
50
+ declare const choiceAnswerSchema: z.ZodObject<{
51
+ type: z.ZodLiteral<"choice">;
52
+ choice: z.ZodString;
53
+ confidence: z.ZodNumber;
54
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
55
+ }, z.core.$strip>;
56
+ type ChoiceAnswer = z.infer<typeof choiceAnswerSchema>;
57
+ /** Public, non-user fields we compare when judging whether two orgs are the same. */
58
+ interface OrgRecordInput {
59
+ name: string;
60
+ domain?: string;
61
+ location?: string;
62
+ description?: string;
63
+ }
64
+ /**
65
+ * State for the org-pair judgment. Both records live under stable named fields
66
+ * so the question can reference them unambiguously.
67
+ */
68
+ declare function buildOrgPairState(a: OrgRecordInput, b: OrgRecordInput): {
69
+ orgA: Partial<OrgRecordInput>;
70
+ orgB: Partial<OrgRecordInput>;
71
+ };
72
+ /** Static Noul question paired with {@link buildOrgPairState}. */
73
+ declare const ORG_PAIR_NOUL_QUESTION: NoulQuestion;
74
+ /**
75
+ * Threshold favouring precision: a wrong merge is worse than a missed one.
76
+ * Validate on labelled pairs (ticket #560) before enabling merges in prod.
77
+ */
78
+ declare const SAME_ORG_THRESHOLD = 0.85;
79
+ /** True when the Noul probability clears the merge threshold. */
80
+ declare function isLikelySameOrg(noul: number, threshold?: number): boolean;
81
+ /** Industry taxonomy. Single source of truth; `other` is the no-match option. */
82
+ declare const INDUSTRY_TAXONOMY: Record<string, string>;
83
+ /** Organisation type taxonomy; `other` is the no-match option. */
84
+ declare const ORG_TYPE_TAXONOMY: Record<string, string>;
85
+ /** Ordered employee-size bands; `unknown` is the no-match option. */
86
+ declare const SIZE_BAND_TAXONOMY: Record<string, string>;
87
+ declare const INDUSTRY_CHOICE_QUESTION: ChoiceQuestion;
88
+ declare const ORG_TYPE_CHOICE_QUESTION: ChoiceQuestion;
89
+ declare const SIZE_BAND_CHOICE_QUESTION: ChoiceQuestion;
90
+ /** Input for firmographic classification. Enrichment text is the primary evidence. */
91
+ interface FirmographicInput {
92
+ name: string;
93
+ domain?: string;
94
+ enrichmentText: string;
95
+ }
96
+ /** State for the firmographic questions. */
97
+ declare function buildFirmographicState(org: FirmographicInput): Partial<FirmographicInput>;
98
+ /**
99
+ * Below this confidence a firmographic field is left unset rather than guessed.
100
+ * Validate per field on labelled orgs (ticket #561).
101
+ */
102
+ declare const FIRMOGRAPHIC_CONFIDENCE_FLOOR = 0.6;
103
+ /**
104
+ * Select the classified option, or `null` to leave the field unset. Returns
105
+ * null when confidence is below the floor, or when the selected option is the
106
+ * taxonomy's no-match option.
107
+ */
108
+ declare function pickChoiceAboveFloor(answer: ChoiceAnswer, opts?: {
109
+ floor?: number;
110
+ noMatch?: string;
111
+ }): string | null;
112
+
113
+ export { type ChoiceAnswer, type ChoiceQuestion, FIRMOGRAPHIC_CONFIDENCE_FLOOR, type FirmographicInput, INDUSTRY_CHOICE_QUESTION, INDUSTRY_TAXONOMY, type NoulAnswer, type NoulQuestion, ORG_PAIR_NOUL_QUESTION, ORG_TYPE_CHOICE_QUESTION, ORG_TYPE_TAXONOMY, type OrgRecordInput, SAME_ORG_THRESHOLD, SIZE_BAND_CHOICE_QUESTION, SIZE_BAND_TAXONOMY, buildFirmographicState, buildOrgPairState, choiceAnswerSchema, isLikelySameOrg, noulAnswerSchema, pickChoiceAboveFloor };
@@ -0,0 +1,97 @@
1
+ import { z } from 'zod';
2
+
3
+ // src/jev/index.ts
4
+ var noulAnswerSchema = z.object({
5
+ type: z.literal("noul"),
6
+ noul: z.number().min(0).max(1)
7
+ });
8
+ var choiceAnswerSchema = z.object({
9
+ type: z.literal("choice"),
10
+ choice: z.string(),
11
+ confidence: z.number().min(0).max(1),
12
+ probabilities: z.record(z.string(), z.number())
13
+ });
14
+ function compact(obj) {
15
+ const out = {};
16
+ for (const [k, v] of Object.entries(obj)) {
17
+ if (v !== void 0 && v !== null && v !== "") out[k] = v;
18
+ }
19
+ return out;
20
+ }
21
+ function buildOrgPairState(a, b) {
22
+ return { orgA: compact(a), orgB: compact(b) };
23
+ }
24
+ var ORG_PAIR_NOUL_QUESTION = {
25
+ type: "noul",
26
+ instructions: "The state holds two organisation records, orgA and orgB. Do they refer to the same real-world organisation?",
27
+ criteria: {
28
+ true: "Same organisation, e.g. a legal-suffix or abbreviation variant of the same name, or the same primary domain.",
29
+ false: "Distinct organisations, e.g. a shared word in the name but different domains, locations, or lines of business."
30
+ }
31
+ };
32
+ var SAME_ORG_THRESHOLD = 0.85;
33
+ function isLikelySameOrg(noul, threshold = SAME_ORG_THRESHOLD) {
34
+ return noul >= threshold;
35
+ }
36
+ var INDUSTRY_TAXONOMY = {
37
+ technology: "Software, IT services, internet, hardware, telecoms",
38
+ finance: "Banking, insurance, investment, fintech, accounting",
39
+ healthcare: "Medical care, pharma, biotech, health services",
40
+ education: "Schools, universities, training, edtech",
41
+ manufacturing: "Industrial production, hardware, materials, automotive",
42
+ retail: "Retail, ecommerce, consumer goods, hospitality, food",
43
+ media: "Media, publishing, entertainment, marketing, advertising",
44
+ energy: "Energy, utilities, oil and gas, renewables",
45
+ construction: "Construction, real estate, architecture, engineering",
46
+ transport: "Transport, logistics, shipping, aviation",
47
+ professional_services: "Legal, consulting, HR, business services",
48
+ public_sector: "Government, defence, public administration",
49
+ nonprofit: "Charities, NGOs, foundations, associations",
50
+ agriculture: "Agriculture, farming, fishing, forestry",
51
+ other: "None of the above, or not enough information to decide"
52
+ };
53
+ var ORG_TYPE_TAXONOMY = {
54
+ company: "For-profit company or corporation",
55
+ nonprofit: "Nonprofit, charity, NGO, or foundation",
56
+ government: "Government body or public-sector entity",
57
+ educational: "School, university, or research institution",
58
+ agency: "Agency, consultancy, or professional-services firm",
59
+ other: "None of the above, or not enough information to decide"
60
+ };
61
+ var SIZE_BAND_TAXONOMY = {
62
+ solo: "1 person",
63
+ small: "2 to 10 people",
64
+ medium: "11 to 50 people",
65
+ large: "51 to 250 people",
66
+ enterprise: "More than 250 people",
67
+ unknown: "Not enough information to decide"
68
+ };
69
+ function choiceQuestion(instructions, criteria) {
70
+ return { type: "choice", instructions, criteria };
71
+ }
72
+ var INDUSTRY_CHOICE_QUESTION = choiceQuestion(
73
+ "Based on the org state, which single industry best describes this organisation?",
74
+ INDUSTRY_TAXONOMY
75
+ );
76
+ var ORG_TYPE_CHOICE_QUESTION = choiceQuestion(
77
+ "Based on the org state, which single type best describes this organisation?",
78
+ ORG_TYPE_TAXONOMY
79
+ );
80
+ var SIZE_BAND_CHOICE_QUESTION = choiceQuestion(
81
+ "Based on the org state, which employee-size band best fits this organisation?",
82
+ SIZE_BAND_TAXONOMY
83
+ );
84
+ function buildFirmographicState(org) {
85
+ return compact(org);
86
+ }
87
+ var FIRMOGRAPHIC_CONFIDENCE_FLOOR = 0.6;
88
+ function pickChoiceAboveFloor(answer, opts = {}) {
89
+ const floor = opts.floor ?? FIRMOGRAPHIC_CONFIDENCE_FLOOR;
90
+ if (answer.confidence < floor) return null;
91
+ if (opts.noMatch !== void 0 && answer.choice === opts.noMatch) return null;
92
+ return answer.choice;
93
+ }
94
+
95
+ export { FIRMOGRAPHIC_CONFIDENCE_FLOOR, INDUSTRY_CHOICE_QUESTION, INDUSTRY_TAXONOMY, ORG_PAIR_NOUL_QUESTION, ORG_TYPE_CHOICE_QUESTION, ORG_TYPE_TAXONOMY, SAME_ORG_THRESHOLD, SIZE_BAND_CHOICE_QUESTION, SIZE_BAND_TAXONOMY, buildFirmographicState, buildOrgPairState, choiceAnswerSchema, isLikelySameOrg, noulAnswerSchema, pickChoiceAboveFloor };
96
+ //# sourceMappingURL=index.js.map
97
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/jev/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * `@singi-labs/sifa-sdk/jev` -- TypeSafe System One (Jev) question builders,\n * taxonomies, result schemas, and threshold predicates for Sifa's organisation\n * intelligence.\n *\n * This subpath is pure: it defines the typed *questions* to ask Jev, validates\n * the *answers*, and holds the policy constants (thresholds, taxonomies) that\n * turn a raw judgment into a decision. It performs no network calls and needs\n * no API key, so it is safe to import anywhere. The actual `systemOne` call\n * lives server-side in sifa-api, which passes an API key that must never reach\n * the browser.\n *\n * Judgment shapes:\n * - Duplicate-org detection: a single Noul over a candidate pair.\n * - Firmographic classification: independent Choice questions (industry, type,\n * size) asked together over one org's state.\n *\n * See docs.typesafe.ai for the wire contract. Typed output guarantees the\n * interface, not truth: validate thresholds on labelled Sifa data before\n * enabling any write.\n */\n\nimport { z } from 'zod';\n\n// ---------------------------------------------------------------------------\n// Wire question types\n// ---------------------------------------------------------------------------\n\n/** A yes/no judgment. `criteria` clarifies what true vs false means. */\nexport interface NoulQuestion {\n type: 'noul';\n instructions: string;\n criteria?: { true: string; false: string };\n}\n\n/** A single-selection judgment. `criteria` maps each option to its description. */\nexport interface ChoiceQuestion {\n type: 'choice';\n instructions: string;\n criteria: Record<string, string>;\n}\n\n// ---------------------------------------------------------------------------\n// Answer schemas (validate Jev's response before code trusts it)\n// ---------------------------------------------------------------------------\n\n/** Jev's Noul answer: `noul` is the probability that the answer is yes (0..1). */\nexport const noulAnswerSchema = z.object({\n type: z.literal('noul'),\n noul: z.number().min(0).max(1),\n});\nexport type NoulAnswer = z.infer<typeof noulAnswerSchema>;\n\n/**\n * Jev's Choice answer: `choice` is the highest-probability option, `confidence`\n * summarises how concentrated the distribution is, `probabilities` sums to 1.\n */\nexport const choiceAnswerSchema = z.object({\n type: z.literal('choice'),\n choice: z.string(),\n confidence: z.number().min(0).max(1),\n probabilities: z.record(z.string(), z.number()),\n});\nexport type ChoiceAnswer = z.infer<typeof choiceAnswerSchema>;\n\n// ---------------------------------------------------------------------------\n// Duplicate-org detection (Noul) -- ticket #560\n// ---------------------------------------------------------------------------\n\n/** Public, non-user fields we compare when judging whether two orgs are the same. */\nexport interface OrgRecordInput {\n name: string;\n domain?: string;\n location?: string;\n description?: string;\n}\n\n/** Build a plain object with only the defined keys (Jev sees no `undefined`). */\nfunction compact<T extends object>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined && v !== null && v !== '') out[k as keyof T] = v as T[keyof T];\n }\n return out;\n}\n\n/**\n * State for the org-pair judgment. Both records live under stable named fields\n * so the question can reference them unambiguously.\n */\nexport function buildOrgPairState(\n a: OrgRecordInput,\n b: OrgRecordInput,\n): { orgA: Partial<OrgRecordInput>; orgB: Partial<OrgRecordInput> } {\n return { orgA: compact(a), orgB: compact(b) };\n}\n\n/** Static Noul question paired with {@link buildOrgPairState}. */\nexport const ORG_PAIR_NOUL_QUESTION: NoulQuestion = {\n type: 'noul',\n instructions:\n 'The state holds two organisation records, orgA and orgB. Do they refer to the same real-world organisation?',\n criteria: {\n true: 'Same organisation, e.g. a legal-suffix or abbreviation variant of the same name, or the same primary domain.',\n false:\n 'Distinct organisations, e.g. a shared word in the name but different domains, locations, or lines of business.',\n },\n};\n\n/**\n * Threshold favouring precision: a wrong merge is worse than a missed one.\n * Validate on labelled pairs (ticket #560) before enabling merges in prod.\n */\nexport const SAME_ORG_THRESHOLD = 0.85;\n\n/** True when the Noul probability clears the merge threshold. */\nexport function isLikelySameOrg(noul: number, threshold: number = SAME_ORG_THRESHOLD): boolean {\n return noul >= threshold;\n}\n\n// ---------------------------------------------------------------------------\n// Firmographic classification (Choice) -- ticket #561\n// ---------------------------------------------------------------------------\n\n/** Industry taxonomy. Single source of truth; `other` is the no-match option. */\nexport const INDUSTRY_TAXONOMY: Record<string, string> = {\n technology: 'Software, IT services, internet, hardware, telecoms',\n finance: 'Banking, insurance, investment, fintech, accounting',\n healthcare: 'Medical care, pharma, biotech, health services',\n education: 'Schools, universities, training, edtech',\n manufacturing: 'Industrial production, hardware, materials, automotive',\n retail: 'Retail, ecommerce, consumer goods, hospitality, food',\n media: 'Media, publishing, entertainment, marketing, advertising',\n energy: 'Energy, utilities, oil and gas, renewables',\n construction: 'Construction, real estate, architecture, engineering',\n transport: 'Transport, logistics, shipping, aviation',\n professional_services: 'Legal, consulting, HR, business services',\n public_sector: 'Government, defence, public administration',\n nonprofit: 'Charities, NGOs, foundations, associations',\n agriculture: 'Agriculture, farming, fishing, forestry',\n other: 'None of the above, or not enough information to decide',\n};\n\n/** Organisation type taxonomy; `other` is the no-match option. */\nexport const ORG_TYPE_TAXONOMY: Record<string, string> = {\n company: 'For-profit company or corporation',\n nonprofit: 'Nonprofit, charity, NGO, or foundation',\n government: 'Government body or public-sector entity',\n educational: 'School, university, or research institution',\n agency: 'Agency, consultancy, or professional-services firm',\n other: 'None of the above, or not enough information to decide',\n};\n\n/** Ordered employee-size bands; `unknown` is the no-match option. */\nexport const SIZE_BAND_TAXONOMY: Record<string, string> = {\n solo: '1 person',\n small: '2 to 10 people',\n medium: '11 to 50 people',\n large: '51 to 250 people',\n enterprise: 'More than 250 people',\n unknown: 'Not enough information to decide',\n};\n\nfunction choiceQuestion(instructions: string, criteria: Record<string, string>): ChoiceQuestion {\n return { type: 'choice', instructions, criteria };\n}\n\nexport const INDUSTRY_CHOICE_QUESTION = choiceQuestion(\n 'Based on the org state, which single industry best describes this organisation?',\n INDUSTRY_TAXONOMY,\n);\n\nexport const ORG_TYPE_CHOICE_QUESTION = choiceQuestion(\n 'Based on the org state, which single type best describes this organisation?',\n ORG_TYPE_TAXONOMY,\n);\n\nexport const SIZE_BAND_CHOICE_QUESTION = choiceQuestion(\n 'Based on the org state, which employee-size band best fits this organisation?',\n SIZE_BAND_TAXONOMY,\n);\n\n/** Input for firmographic classification. Enrichment text is the primary evidence. */\nexport interface FirmographicInput {\n name: string;\n domain?: string;\n enrichmentText: string;\n}\n\n/** State for the firmographic questions. */\nexport function buildFirmographicState(org: FirmographicInput): Partial<FirmographicInput> {\n return compact(org);\n}\n\n/**\n * Below this confidence a firmographic field is left unset rather than guessed.\n * Validate per field on labelled orgs (ticket #561).\n */\nexport const FIRMOGRAPHIC_CONFIDENCE_FLOOR = 0.6;\n\n/**\n * Select the classified option, or `null` to leave the field unset. Returns\n * null when confidence is below the floor, or when the selected option is the\n * taxonomy's no-match option.\n */\nexport function pickChoiceAboveFloor(\n answer: ChoiceAnswer,\n opts: { floor?: number; noMatch?: string } = {},\n): string | null {\n const floor = opts.floor ?? FIRMOGRAPHIC_CONFIDENCE_FLOOR;\n if (answer.confidence < floor) return null;\n if (opts.noMatch !== undefined && answer.choice === opts.noMatch) return null;\n return answer.choice;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singi-labs/sifa-sdk",
3
- "version": "0.19.23",
3
+ "version": "0.19.24",
4
4
  "description": "Sifa SDK — public client library for the Sifa AppView on AT Protocol. Shared by sifa-web and sifa-app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -162,6 +162,16 @@
162
162
  "default": "./dist/resume/index.cjs"
163
163
  }
164
164
  },
165
+ "./jev": {
166
+ "import": {
167
+ "types": "./dist/jev/index.d.ts",
168
+ "default": "./dist/jev/index.js"
169
+ },
170
+ "require": {
171
+ "types": "./dist/jev/index.d.cts",
172
+ "default": "./dist/jev/index.cjs"
173
+ }
174
+ },
165
175
  "./package.json": "./package.json"
166
176
  },
167
177
  "main": "./dist/index.cjs",