Analyze Attio Workspace
Analyze Attio People by sampling recently active records to discover attributes and data patterns
Source Code
import fs from "fs";
const [outputPath = "session/attio-analysis.json", focusField = ""] =
process.argv.slice(2);
const ATTIO_API = "https://api.attio.com/v2";
const headers = {
Authorization: "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
};
/**
* Infer attribute type from Attio value structure
*/
function inferType(value) {
if (!value || !Array.isArray(value) || value.length === 0) return "unknown";
const first = value[0];
if (!first) return "unknown";
if (first.email_address !== undefined) return "email";
if (first.original_phone_number !== undefined) return "phone";
if (first.full_name !== undefined) return "personal-name";
if (first.target_object !== undefined)
return `record-reference → ${first.target_object}`;
if (first.option !== undefined) return "select";
if (first.domain !== undefined) return "domain";
if (first.line_1 !== undefined) return "location";
if (first.currency_value !== undefined) return "currency";
if (typeof first.value === "number") return "number";
if (typeof first.value === "boolean") return "checkbox";
if (typeof first.value === "string") {
// Check if it looks like a date
if (/^\d{4}-\d{2}-\d{2}/.test(first.value)) return "date";
return "text";
}
if (first.interaction_type !== undefined) return "interaction";
if (first.referenced_actor_id !== undefined) return "actor-reference";
return "unknown";
}
/**
* Extract display value from Attio value structure
*/
function extractDisplayValue(value) {
if (!value || !Array.isArray(value) || value.length === 0) return null;
const first = value[0];
if (!first) return null;
if (first.value !== undefined) return first.value;
if (first.full_name !== undefined) return first.full_name;
if (first.email_address !== undefined) return first.email_address;
if (first.original_phone_number !== undefined)
return first.original_phone_number;
if (first.target_object !== undefined) return `[${first.target_object}]`;
if (first.option !== undefined) return first.option;
if (first.domain !== undefined) return first.domain;
if (first.locality !== undefined) return first.locality;
return null;
}
/**
* Check if a record has a populated value for a given field
*/
function hasPopulatedField(record, fieldSlug) {
const value = record.values?.[fieldSlug];
return Array.isArray(value) && value.length > 0 && value[0] !== null;
}
console.log("Analyzing Attio workspace...");
try {
// Build query - optionally filter by focus field
const queryBody = {
limit: 100,
sorts: [{ attribute: "updated_at", direction: "desc" }],
};
// If focus field provided, add filter to only get records with that field populated
if (focusField && focusField.trim()) {
console.log(`Focusing on records with '${focusField}' populated...`);
queryBody.filter = {
[focusField]: { $not_empty: true },
};
}
// Query records
console.log(
focusField
? `Sampling 100 records with '${focusField}' populated...`
: "Sampling 100 recently active records..."
);
const queryRes = await fetch(`${ATTIO_API}/objects/people/records/query`, {
method: "POST",
headers,
body: JSON.stringify(queryBody),
});
if (!queryRes.ok) {
const errorText = await queryRes.text();
console.error(`Query failed: ${queryRes.status}`);
console.error(errorText);
throw new Error(`Query failed: ${queryRes.status}`);
}
const queryData = await queryRes.json();
const records = queryData.data || [];
const sampleSize = records.length;
console.log(` Sampled ${sampleSize} records`);
// Discover attributes from actual record values
const discoveredAttributes = new Map();
for (const record of records) {
const values = record.values || {};
for (const [attrSlug, attrValue] of Object.entries(values)) {
// Initialize attribute tracking if first time seeing it
if (!discoveredAttributes.has(attrSlug)) {
discoveredAttributes.set(attrSlug, {
slug: attrSlug,
type: null,
populatedCount: 0,
sampleValues: [],
});
}
const attr = discoveredAttributes.get(attrSlug);
// Check if attribute has a value
const hasValue =
Array.isArray(attrValue) &&
attrValue.length > 0 &&
attrValue[0] !== null;
if (hasValue) {
attr.populatedCount++;
// Infer type from first populated value
if (!attr.type || attr.type === "unknown") {
attr.type = inferType(attrValue);
}
// Collect sample values (up to 10 unique per attribute)
if (attr.sampleValues.length < 10) {
const displayValue = extractDisplayValue(attrValue);
if (displayValue && !attr.sampleValues.includes(displayValue)) {
attr.sampleValues.push(displayValue);
}
}
}
}
}
// Calculate population percentages and build stats array
const attributeStats = [];
const linkedObjects = [];
for (const [slug, attr] of discoveredAttributes) {
const percentage =
sampleSize > 0 ? Math.round((attr.populatedCount / sampleSize) * 100) : 0;
attributeStats.push({
slug,
type: attr.type || "unknown",
population: percentage,
populatedCount: attr.populatedCount,
sampleValues: attr.sampleValues,
});
// Track record references (linked objects)
if (attr.type && attr.type.startsWith("record-reference")) {
const targetObject = attr.type.split(" → ")[1] || "unknown";
linkedObjects.push({
attribute: slug,
targetObject,
population: percentage,
});
}
}
// Sort by population (highest first)
attributeStats.sort((a, b) => b.population - a.population);
// Extract focus field stats if provided
let focusFieldStats = null;
if (focusField && focusField.trim()) {
const focusAttr = attributeStats.find((a) => a.slug === focusField);
if (focusAttr) {
focusFieldStats = {
slug: focusAttr.slug,
type: focusAttr.type,
population: focusAttr.population,
uniqueValues: focusAttr.sampleValues,
};
}
}
// Extract common patterns for key fields
const jobTitleCounts = {};
const companyCounts = {};
for (const record of records) {
const values = record.values || {};
// Job titles
const jobTitle = values.job_title?.[0]?.value;
if (jobTitle) {
jobTitleCounts[jobTitle] = (jobTitleCounts[jobTitle] || 0) + 1;
}
// Companies (if linked)
const company = values.company?.[0];
if (company?.target_record_id) {
companyCounts[company.target_record_id] =
(companyCounts[company.target_record_id] || 0) + 1;
}
}
const topJobTitles = Object.entries(jobTitleCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([title, count]) => ({
title,
count,
percentage: Math.round((count / sampleSize) * 100),
}));
const uniqueCompanies = Object.keys(companyCounts).length;
// Save analysis
const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
if (dir) {
fs.mkdirSync(dir, { recursive: true });
}
const analysis = {
analyzedAt: new Date().toISOString(),
sampleSize,
sampleType: focusField ? `filtered_by_${focusField}` : "recently_updated",
focusField: focusFieldStats,
attributes: attributeStats,
linkedObjects,
patterns: {
topJobTitles,
uniqueCompaniesInSample: uniqueCompanies,
},
};
fs.writeFileSync(outputPath, JSON.stringify(analysis, null, 2));
// Log summary
console.log(`\n✓ Attio analysis complete`);
if (focusFieldStats) {
console.log(
` Focus field '${focusField}': ${focusFieldStats.uniqueValues.length} unique values`
);
console.log(
` Values: ${focusFieldStats.uniqueValues.slice(0, 5).join(", ")}`
);
}
console.log(
` Discovered ${attributeStats.length} attributes from record values`
);
console.log(` Linked objects: ${linkedObjects.length}`);
console.log(` Sample size: ${sampleSize} records`);
const wellPopulated = attributeStats.filter((a) => a.population >= 50);
console.log(` Well-populated attributes (≥50%): ${wellPopulated.length}`);
if (attributeStats.length > 0) {
console.log(`\n Top attributes by population:`);
for (const attr of attributeStats.slice(0, 8)) {
console.log(` ${attr.slug}: ${attr.population}% (${attr.type})`);
}
}
if (topJobTitles.length > 0) {
console.log(`\n Top job titles:`);
for (const jt of topJobTitles.slice(0, 5)) {
console.log(` ${jt.title}: ${jt.count}`);
}
}
console.log(`\n Written to: ${outputPath}`);
console.log(
JSON.stringify({
success: true,
outputPath,
attributeCount: attributeStats.length,
linkedObjectCount: linkedObjects.length,
sampleSize,
focusField: focusField || null,
})
);
} catch (error) {
console.error("Error analyzing Attio:", error.message);
throw error;
} import fs from "fs";
const [outputPath = "session/attio-analysis.json", focusField = ""] =
process.argv.slice(2);
const ATTIO_API = "https://api.attio.com/v2";
const headers = {
Authorization: "Bearer PLACEHOLDER_TOKEN",
"Content-Type": "application/json",
};
/**
* Infer attribute type from Attio value structure
*/
function inferType(value) {
if (!value || !Array.isArray(value) || value.length === 0) return "unknown";
const first = value[0];
if (!first) return "unknown";
if (first.email_address !== undefined) return "email";
if (first.original_phone_number !== undefined) return "phone";
if (first.full_name !== undefined) return "personal-name";
if (first.target_object !== undefined)
return `record-reference → ${first.target_object}`;
if (first.option !== undefined) return "select";
if (first.domain !== undefined) return "domain";
if (first.line_1 !== undefined) return "location";
if (first.currency_value !== undefined) return "currency";
if (typeof first.value === "number") return "number";
if (typeof first.value === "boolean") return "checkbox";
if (typeof first.value === "string") {
// Check if it looks like a date
if (/^\d{4}-\d{2}-\d{2}/.test(first.value)) return "date";
return "text";
}
if (first.interaction_type !== undefined) return "interaction";
if (first.referenced_actor_id !== undefined) return "actor-reference";
return "unknown";
}
/**
* Extract display value from Attio value structure
*/
function extractDisplayValue(value) {
if (!value || !Array.isArray(value) || value.length === 0) return null;
const first = value[0];
if (!first) return null;
if (first.value !== undefined) return first.value;
if (first.full_name !== undefined) return first.full_name;
if (first.email_address !== undefined) return first.email_address;
if (first.original_phone_number !== undefined)
return first.original_phone_number;
if (first.target_object !== undefined) return `[${first.target_object}]`;
if (first.option !== undefined) return first.option;
if (first.domain !== undefined) return first.domain;
if (first.locality !== undefined) return first.locality;
return null;
}
/**
* Check if a record has a populated value for a given field
*/
function hasPopulatedField(record, fieldSlug) {
const value = record.values?.[fieldSlug];
return Array.isArray(value) && value.length > 0 && value[0] !== null;
}
console.log("Analyzing Attio workspace...");
try {
// Build query - optionally filter by focus field
const queryBody = {
limit: 100,
sorts: [{ attribute: "updated_at", direction: "desc" }],
};
// If focus field provided, add filter to only get records with that field populated
if (focusField && focusField.trim()) {
console.log(`Focusing on records with '${focusField}' populated...`);
queryBody.filter = {
[focusField]: { $not_empty: true },
};
}
// Query records
console.log(
focusField
? `Sampling 100 records with '${focusField}' populated...`
: "Sampling 100 recently active records..."
);
const queryRes = await fetch(`${ATTIO_API}/objects/people/records/query`, {
method: "POST",
headers,
body: JSON.stringify(queryBody),
});
if (!queryRes.ok) {
const errorText = await queryRes.text();
console.error(`Query failed: ${queryRes.status}`);
console.error(errorText);
throw new Error(`Query failed: ${queryRes.status}`);
}
const queryData = await queryRes.json();
const records = queryData.data || [];
const sampleSize = records.length;
console.log(` Sampled ${sampleSize} records`);
// Discover attributes from actual record values
const discoveredAttributes = new Map();
for (const record of records) {
const values = record.values || {};
for (const [attrSlug, attrValue] of Object.entries(values)) {
// Initialize attribute tracking if first time seeing it
if (!discoveredAttributes.has(attrSlug)) {
discoveredAttributes.set(attrSlug, {
slug: attrSlug,
type: null,
populatedCount: 0,
sampleValues: [],
});
}
const attr = discoveredAttributes.get(attrSlug);
// Check if attribute has a value
const hasValue =
Array.isArray(attrValue) &&
attrValue.length > 0 &&
attrValue[0] !== null;
if (hasValue) {
attr.populatedCount++;
// Infer type from first populated value
if (!attr.type || attr.type === "unknown") {
attr.type = inferType(attrValue);
}
// Collect sample values (up to 10 unique per attribute)
if (attr.sampleValues.length < 10) {
const displayValue = extractDisplayValue(attrValue);
if (displayValue && !attr.sampleValues.includes(displayValue)) {
attr.sampleValues.push(displayValue);
}
}
}
}
}
// Calculate population percentages and build stats array
const attributeStats = [];
const linkedObjects = [];
for (const [slug, attr] of discoveredAttributes) {
const percentage =
sampleSize > 0 ? Math.round((attr.populatedCount / sampleSize) * 100) : 0;
attributeStats.push({
slug,
type: attr.type || "unknown",
population: percentage,
populatedCount: attr.populatedCount,
sampleValues: attr.sampleValues,
});
// Track record references (linked objects)
if (attr.type && attr.type.startsWith("record-reference")) {
const targetObject = attr.type.split(" → ")[1] || "unknown";
linkedObjects.push({
attribute: slug,
targetObject,
population: percentage,
});
}
}
// Sort by population (highest first)
attributeStats.sort((a, b) => b.population - a.population);
// Extract focus field stats if provided
let focusFieldStats = null;
if (focusField && focusField.trim()) {
const focusAttr = attributeStats.find((a) => a.slug === focusField);
if (focusAttr) {
focusFieldStats = {
slug: focusAttr.slug,
type: focusAttr.type,
population: focusAttr.population,
uniqueValues: focusAttr.sampleValues,
};
}
}
// Extract common patterns for key fields
const jobTitleCounts = {};
const companyCounts = {};
for (const record of records) {
const values = record.values || {};
// Job titles
const jobTitle = values.job_title?.[0]?.value;
if (jobTitle) {
jobTitleCounts[jobTitle] = (jobTitleCounts[jobTitle] || 0) + 1;
}
// Companies (if linked)
const company = values.company?.[0];
if (company?.target_record_id) {
companyCounts[company.target_record_id] =
(companyCounts[company.target_record_id] || 0) + 1;
}
}
const topJobTitles = Object.entries(jobTitleCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([title, count]) => ({
title,
count,
percentage: Math.round((count / sampleSize) * 100),
}));
const uniqueCompanies = Object.keys(companyCounts).length;
// Save analysis
const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
if (dir) {
fs.mkdirSync(dir, { recursive: true });
}
const analysis = {
analyzedAt: new Date().toISOString(),
sampleSize,
sampleType: focusField ? `filtered_by_${focusField}` : "recently_updated",
focusField: focusFieldStats,
attributes: attributeStats,
linkedObjects,
patterns: {
topJobTitles,
uniqueCompaniesInSample: uniqueCompanies,
},
};
fs.writeFileSync(outputPath, JSON.stringify(analysis, null, 2));
// Log summary
console.log(`\n✓ Attio analysis complete`);
if (focusFieldStats) {
console.log(
` Focus field '${focusField}': ${focusFieldStats.uniqueValues.length} unique values`
);
console.log(
` Values: ${focusFieldStats.uniqueValues.slice(0, 5).join(", ")}`
);
}
console.log(
` Discovered ${attributeStats.length} attributes from record values`
);
console.log(` Linked objects: ${linkedObjects.length}`);
console.log(` Sample size: ${sampleSize} records`);
const wellPopulated = attributeStats.filter((a) => a.population >= 50);
console.log(` Well-populated attributes (≥50%): ${wellPopulated.length}`);
if (attributeStats.length > 0) {
console.log(`\n Top attributes by population:`);
for (const attr of attributeStats.slice(0, 8)) {
console.log(` ${attr.slug}: ${attr.population}% (${attr.type})`);
}
}
if (topJobTitles.length > 0) {
console.log(`\n Top job titles:`);
for (const jt of topJobTitles.slice(0, 5)) {
console.log(` ${jt.title}: ${jt.count}`);
}
}
console.log(`\n Written to: ${outputPath}`);
console.log(
JSON.stringify({
success: true,
outputPath,
attributeCount: attributeStats.length,
linkedObjectCount: linkedObjects.length,
sampleSize,
focusField: focusField || null,
})
);
} catch (error) {
console.error("Error analyzing Attio:", error.message);
throw error;
}