Appearance
Exercise 3: Build Deterministic Appraisal Logic
In this exercise, you'll create an Apex class that implements the retailer's vehicle appraisal logic. The logic is intentionally simplified for this workshop and isn't intended for real-world appraisals.
The appraisal takes a vehicle's VIN (Vehicle Identification Number), odometer reading, and condition as inputs and calculates a trade-in offer based on:
- Vehicle manufacturer and age
- Mileage and condition
- Reconditioning costs and target profit margin
The class returns a trade-in offer, an offer range, and a detailed breakdown of the adjustments behind the calculation.
The logic is simplified, but the principle is real: Business logic implemented in Salesforce produces consistent, predictable results that AI assistants can use.
TIP
Not an Apex developer? No problem! You'll copy and paste the provided code, so you won't need to write or modify any Apex yourself.
Step 1: Open Web Console
In Salesforce, click the gear icon in the upper-right corner, then select Web Console. It opens in a new browser window.
TIP
Don't see Web Console in the menu?
From Setup, enter
Web Consolein the Quick Find box, then select Web Console. Turn on Enable Web Console, reload the page, and click the gear icon again.Wait for Web Console to load. The Explorer panel on the left displays a project containing a force-app folder.
Step 2: Create the Apex Class
Press Ctrl+Shift+P (Windows) or Cmd+Shift+P (Mac) to open the Command Palette.
Enter
SFDX: Create Apex Classand press Enter.Select DefaultApexClass and press Enter.
Enter
TradeInAppraisalas the class name and press Enter. If prompted to select a directory, press Enter to accept the default:force-app/main/default/classes.Web Console opens the new
TradeInAppraisal.clsfile. Select all the existing code (Ctrl+A on Windows or Cmd+A on Mac), then paste the following code:apexglobal with sharing class TradeInAppraisal { @InvocableMethod( label='Appraise Trade-In' description='Decodes a vehicle identification number (VIN) and appraises the vehicle for a trade-in, based on its age, odometer reading, and condition. Returns the vehicle, the trade-in offer and offer range, and every adjustment behind the offer.' ) global static List<Output> appraiseTradeIn(List<Input> inputs) { List<Output> outputs = new List<Output>(); for (Input input : inputs) { outputs.add(appraise(input)); } return outputs; } // Where a vehicle was built, from the first character of the VIN private static final Map<String, String> REGIONS = new Map<String, String>{ '1' => 'United States', '4' => 'United States', '5' => 'United States', '2' => 'Canada', '3' => 'Mexico', '6' => 'Australia', '9' => 'Brazil', 'J' => 'Japan', 'K' => 'South Korea', 'L' => 'China', 'M' => 'India', 'S' => 'United Kingdom', 'T' => 'Central Europe', 'V' => 'France or Spain', 'W' => 'Germany', 'Y' => 'Sweden or Finland', 'Z' => 'Italy' }; // Manufacturer from the World Manufacturer Identifier, the first 3 (or 2) characters of the VIN private static final Map<String, String> MANUFACTURERS = new Map<String, String>{ 'WVW' => 'Volkswagen', 'WV1' => 'Volkswagen', 'WV2' => 'Volkswagen', '3VW' => 'Volkswagen', '9BW' => 'Volkswagen', 'LSV' => 'Volkswagen', 'WAU' => 'Audi', 'TRU' => 'Audi', 'WBA' => 'BMW', 'WBS' => 'BMW', 'WBY' => 'BMW', 'WDD' => 'Mercedes-Benz', 'WDB' => 'Mercedes-Benz', 'W1K' => 'Mercedes-Benz', 'W1N' => 'Mercedes-Benz', 'WP0' => 'Porsche', 'WP1' => 'Porsche', 'WF0' => 'Ford', '1FA' => 'Ford', '1FT' => 'Ford', '1FM' => 'Ford', 'W0L' => 'Opel', 'VF1' => 'Renault', 'VF3' => 'Peugeot', 'VF7' => 'Citroen', 'VSS' => 'SEAT', 'TMB' => 'Skoda', 'ZFA' => 'Fiat', 'ZAR' => 'Alfa Romeo', 'YV1' => 'Volvo', 'SAJ' => 'Jaguar', 'SAL' => 'Land Rover', '5YJ' => 'Tesla', '7SA' => 'Tesla', 'LRW' => 'Tesla', 'XP7' => 'Tesla', '1G1' => 'Chevrolet', '1GC' => 'Chevrolet', '1HG' => 'Honda', '4T1' => 'Toyota', 'SB1' => 'Toyota', 'VNK' => 'Toyota', 'SJN' => 'Nissan', 'TMA' => 'Hyundai', 'U5Y' => 'Kia', 'MA3' => 'Suzuki', 'JS2' => 'Suzuki', 'MAL' => 'Hyundai' }; private static final Map<String, String> MANUFACTURER_PREFIXES = new Map<String, String>{ 'JT' => 'Toyota', 'JH' => 'Honda', 'JN' => 'Nissan', 'JM' => 'Mazda', 'KM' => 'Hyundai', 'KN' => 'Kia', 'JF' => 'Subaru' }; // Example price guide: a typical new price per manufacturer. In a real org, this comes from your pricing provider. private static final Map<String, Decimal> NEW_PRICES = new Map<String, Decimal>{ 'Porsche' => 95000, 'Land Rover' => 65000, 'Mercedes-Benz' => 55000, 'Jaguar' => 55000, 'BMW' => 52000, 'Tesla' => 50000, 'Audi' => 48000, 'Volvo' => 47000, 'Alfa Romeo' => 42000, 'Ford' => 34000, 'Volkswagen' => 33000, 'Chevrolet' => 32000, 'Toyota' => 31000, 'Honda' => 30000, 'Subaru' => 30000, 'Mazda' => 29000, 'Nissan' => 28000, 'Skoda' => 28000, 'Hyundai' => 27000, 'Kia' => 26000, 'Peugeot' => 26000, 'Renault' => 25000, 'Opel' => 25000, 'SEAT' => 25000, 'Citroen' => 24000, 'Fiat' => 21000, 'Suzuki' => 19000 }; // Your dealership's appraisal rules private static final Decimal YEARLY_DEPRECIATION = 0.85; // a car keeps 85% of its value each year private static final Integer EXPECTED_DISTANCE_PER_YEAR = 15000; private static final Decimal DISTANCE_ADJUSTMENT_PER_10000 = 0.03; // 3% per 10,000 above or below expected private static final Decimal RECONDITIONING_AND_MARGIN = 0.12; // 12% for reconditioning and margin private static final Map<String, Decimal> CONDITION_FACTORS = new Map<String, Decimal>{ 'Excellent' => 1.05, 'Good' => 1.0, 'Fair' => 0.9, 'Poor' => 0.75 }; private static final Map<String, Integer> CONDITION_SCORES = new Map<String, Integer>{ 'Excellent' => 95, 'Good' => 75, 'Fair' => 50, 'Poor' => 25 }; private static Output appraise(Input input) { Output output = new Output(); String vin = input.vin == null ? '' : input.vin.toUpperCase().replaceAll('[^A-Z0-9]', ''); if (!Pattern.matches('[A-HJ-NPR-Z0-9]{17}', vin)) { output.isSuccess = false; output.message = 'A VIN has 17 letters and digits, without I, O, or Q.'; return output; } Integer modelYear = decodeModelYear(vin.substring(9, 10)); String manufacturer = MANUFACTURERS.get(vin.substring(0, 3)); if (manufacturer == null) { manufacturer = MANUFACTURER_PREFIXES.get(vin.substring(0, 2)); } String origin = REGIONS.get(vin.substring(0, 1)); String condition = input.condition == null ? '' : input.condition.trim().toLowerCase().capitalize(); if (!CONDITION_FACTORS.containsKey(condition)) { condition = 'Good'; } Integer age = modelYear == null ? 5 : Math.max(0, Date.today().year() - modelYear); Integer expectedDistance = Math.max(age, 1) * EXPECTED_DISTANCE_PER_YEAR; Integer odometer = input.odometer == null ? expectedDistance : input.odometer; Decimal newPrice = NEW_PRICES.containsKey(manufacturer) ? NEW_PRICES.get(manufacturer) : 30000; Appraisal appraisal = new Appraisal(); appraisal.adjustments = new List<Adjustment>(); appraisal.adjustments.add(new Adjustment('Typical new price', 'Price guide for ' + (manufacturer == null ? 'this vehicle' : manufacturer), newPrice, false)); Decimal value = newPrice * Decimal.valueOf(Math.pow(YEARLY_DEPRECIATION.doubleValue(), age)); appraisal.adjustments.add(new Adjustment('Age', age + (age == 1 ? ' year' : ' years'), value - newPrice, true)); Decimal distanceFactor = (Decimal.valueOf(expectedDistance - odometer) / 10000) * DISTANCE_ADJUSTMENT_PER_10000; distanceFactor = Math.min(0.2, Math.max(-0.3, distanceFactor)); Decimal distanceAdjustment = value * distanceFactor; appraisal.adjustments.add(new Adjustment('Odometer', formatNumber(odometer) + ' vs. ' + formatNumber(expectedDistance) + ' expected', distanceAdjustment, true)); value += distanceAdjustment; Decimal conditionAdjustment = value * (CONDITION_FACTORS.get(condition) - 1); appraisal.adjustments.add(new Adjustment('Condition', condition, conditionAdjustment, true)); value += conditionAdjustment; Decimal marginAdjustment = -value * RECONDITIONING_AND_MARGIN; appraisal.adjustments.add(new Adjustment('Reconditioning and margin', '12% of market value', marginAdjustment, true)); Decimal offer = roundTo100(value + marginAdjustment); appraisal.vin = vin; appraisal.manufacturer = manufacturer == null ? 'Unknown manufacturer' : manufacturer; appraisal.modelYear = modelYear == null ? 'Unknown year' : String.valueOf(modelYear); appraisal.vehicle = appraisal.modelYear + ' ' + appraisal.manufacturer; appraisal.origin = origin == null ? 'Unknown origin' : 'Built in ' + origin; appraisal.odometer = formatNumber(odometer); appraisal.condition = condition; appraisal.conditionScore = CONDITION_SCORES.get(condition); appraisal.marketValue = formatMoney(roundTo100(value)); appraisal.offer = formatMoney(offer); appraisal.offerRange = formatMoney(roundTo100(offer * 0.95)) + ' to ' + formatMoney(roundTo100(offer * 1.05)); output.isSuccess = true; output.message = 'Appraised the ' + appraisal.vehicle + ' with VIN ' + vin + '.'; output.appraisal = appraisal; return output; } // The 10th character of a VIN encodes the model year in a 30-year cycle private static Integer decodeModelYear(String code) { Integer index = 'ABCDEFGHJKLMNPRSTVWXY123456789'.indexOf(code); if (index < 0) { return null; } Integer year = 1980 + index; while (year + 30 <= Date.today().year() + 1) { year += 30; } return year; } private static Decimal roundTo100(Decimal amount) { return (amount / 100).round(System.RoundingMode.HALF_UP) * 100; } private static String formatNumber(Decimal amount) { return amount.setScale(0).format(); } private static String formatMoney(Decimal amount) { return UserInfo.getDefaultCurrency() + ' ' + formatNumber(amount); } global class Input { @InvocableVariable(label='VIN' description='The 17-character vehicle identification number' required=true) public String vin; @InvocableVariable(label='Odometer Reading' description='The distance on the odometer') public Integer odometer; @InvocableVariable(label='Condition' description='Excellent, Good, Fair, or Poor. Defaults to Good.') public String condition; } global class Output { @InvocableVariable(label='Success' description='Whether the appraisal succeeded') public Boolean isSuccess; @InvocableVariable(label='Message' description='A success or error message') public String message; @InvocableVariable(label='Appraisal' description='The vehicle and the trade-in offer') public Appraisal appraisal; } global class Appraisal { @InvocableVariable(label='VIN') public String vin; @InvocableVariable(label='Vehicle' description='Model year and manufacturer') public String vehicle; @InvocableVariable(label='Manufacturer') public String manufacturer; @InvocableVariable(label='Model Year') public String modelYear; @InvocableVariable(label='Origin' description='Where the vehicle was built') public String origin; @InvocableVariable(label='Odometer Reading') public String odometer; @InvocableVariable(label='Condition') public String condition; @InvocableVariable(label='Condition Score' description='The condition as a score from 0 to 100') public Integer conditionScore; @InvocableVariable(label='Market Value') public String marketValue; @InvocableVariable(label='Trade-In Offer') public String offer; @InvocableVariable(label='Offer Range') public String offerRange; @InvocableVariable(label='Adjustments' description='Every step from the new price to the offer') public List<Adjustment> adjustments; } global class Adjustment { @InvocableVariable(label='Factor') public String factor; @InvocableVariable(label='Detail') public String detail; @InvocableVariable(label='Amount') public String amount; public Adjustment() { } public Adjustment(String factor, String detail, Decimal amount, Boolean signed) { this.factor = factor; this.detail = detail; String formatted = formatMoney(amount.abs()); this.amount = !signed ? formatted : (amount < 0 ? '- ' + formatted : '+ ' + formatted); } } }Save the file by pressing Ctrl+S (Windows) or Cmd+S (Mac). Web Console automatically deploys the Apex class to your Salesforce org.
Code Highlights
You don't need to understand Apex to follow along. Here are the key things the class does:
@InvocableMethod: Exposes the Apex method as an action called Appraise Trade-In. In the next exercise, you'll make this action available to your AI assistant as an MCP tool. The label and description help AI assistants understand when to use the tool.- Inputs: Accepts a vehicle's VIN, odometer reading, and optional condition.
- VIN decoding: Uses the VIN to identify the vehicle's manufacturer, model year, and region of manufacture.
- Business rules: Calculates a trade-in offer based on the vehicle's age, mileage, condition, and the retailer's pricing rules.
- Outputs: Returns the vehicle details, trade-in offer, offer range, condition score, and a breakdown of the adjustments used in the calculation.
Summary
You created the TradeInAppraisal Apex class in Web Console using the provided code.
You saved the class to Salesforce, making the Appraise Trade-In action available in your org.
Next, you'll expose that action as an MCP tool your AI assistant can use.