Compare commits

..

2 Commits

Author SHA1 Message Date
paulh e058dedb82 Create Appraiser Case custom record and DocGen service 2026-04-05 14:31:51 -04:00
paulh 1e532029fa Add Appraiser Review Letter template and XML merge data 2026-04-05 14:22:57 -04:00
38 changed files with 1043 additions and 114 deletions

View File

@ -0,0 +1 @@
<EFBFBD>覆骒陡<EFBFBD>'<27>

View File

@ -0,0 +1,71 @@
# DocuSign CLM Template Mapping Snippet
This project assumes Salesforce is the system of record and DocuSign CLM doc gen receives a payload built from `Appraiser_Case__c` and its related `Appraiser_Deficiency__c` records.
## Suggested Merge Payload
```json
{
"templateKey": "APPRAISER_REVIEW_LETTER",
"recordId": "a01XXXXXXXXXXXX",
"sourceObject": "Appraiser_Case__c",
"mergeData": {
"appraiserCaseNumber": "AC-000123",
"appraiserFieldReviewDate": "2026-04-01",
"propertyStreet": "123 Main St",
"propertyCity": "Ottawa",
"propertyStateProvince": "ON",
"propertyPostalCode": "K1A 0A1",
"propertyCountry": "Canada",
"propertyAddressSingleLine": "123 Main St | Ottawa, ON, K1A 0A1 | Canada",
"deficiencies": [
{
"deficiencyNumber": "1",
"description": "Missing comparable sale analysis",
"resolution": "Provide updated comparable sales section",
"sortOrder": 1
}
]
}
}
```
## Suggested Template Tokens
Use the actual DocuSign CLM token syntax used in your tenant, but the field model should be equivalent to:
- `{{mergeData.appraiserCaseNumber}}`
- `{{mergeData.appraiserFieldReviewDate}}`
- `{{mergeData.propertyStreet}}`
- `{{mergeData.propertyCity}}`
- `{{mergeData.propertyStateProvince}}`
- `{{mergeData.propertyPostalCode}}`
- `{{mergeData.propertyCountry}}`
- `{{mergeData.propertyAddressSingleLine}}`
### Deficiency Repeat Block
```handlebars
{{#each mergeData.deficiencies}}
| {{deficiencyNumber}} | {{description}} | {{resolution}} |
{{/each}}
```
## Recommended Letter Sections
1. Header / logo block
2. Review metadata
- Case number
- Field review date
- Property address
3. Deficiency table
- Deficiency number
- Description
- Resolution
4. Closing / signature block
## Notes
- Keep `deficiencies` as a repeatable child collection, not a flattened text blob.
- If DocuSign CLM requires a REST callout payload, `AppraiserCaseDocGenService.buildDocGenRequestJson()` is a good source payload to hand to your integration layer.
- If your CLM tenant uses a different collection token syntax, map the same logical field names there.

View File

@ -1,39 +1,11 @@
# Deployment & Testing — Appraiser Review Letter
## Implemented Salesforce Metadata
- Parent object: Appraiser_Case__c (label: Appraiser Case)
- Name field (auto number): Appraiser Case Number (AC-{00000})
- Fields on Appraiser Case:
- Appraiser_Field_Review_Date__c (Date)
- Property_Address__c (Text 255)
- Child object for repeatable deficiencies: Appraiser_Case_Deficiency__c
- Fields on Appraiser Case Deficiency:
- Appraiser_Case__c (Master-Detail to Appraiser_Case__c)
- Deficiency_Number__c (Number)
- Description__c (Long Text Area)
- Resolution__c (Long Text Area)
- Permission set: Appraiser_Case_Access
- Apex Classes:
- AppraiserCasePayloadBuilder: Transforms Salesforce data to CLM merge payload
- AppraiserCasePayloadBuilderTest: Unit tests for payload builder
- CLMDocGenCallout: HTTP integration with DocuSign CLM API
## Deployment Steps
1. Deploy custom objects & fields
2. Deploy Apex classes (included in manifest)
3. Configure Named Credentials for CLM API access
4. Configure Remote Site Settings for CLM instance
5. Map merge fields to object schema
6. Configure CLM template and connect API
7. Test with sample Appraiser Case records
### Suggested CLI Deploy Commands
1. Authenticate to your org:
- sf org login web --alias appraiser-dev
2. Validate source deploy:
- sf project deploy validate --target-org appraiser-dev --manifest manifest/package.xml
3. Deploy metadata:
- sf project deploy start --target-org appraiser-dev --manifest manifest/package.xml
2. Copy updated template files into Salesforce docs directory
3. Map merge fields to object schema
4. Configure CLM template and connect API
5. Test with sample Appraisal and Deficiency objects
---
@ -43,39 +15,6 @@
- Test table rendering for arrays (DeficiencyList, ReviewerComments)
- Document any failed merges or formatting gaps
## CLM Data Mapping Starter
- AppraiserCaseNumber -> Appraiser_Case__c.Name
- AppraiserFieldReviewDate -> Appraiser_Case__c.Appraiser_Field_Review_Date__c
- PropertyAddress -> Appraiser_Case__c.Property_Address__c
- DeficiencyList[] -> Appraiser_Case__c.Deficiencies__r
- deficiencyNumber -> Deficiency_Number__c
- description -> Description__c
- resolution -> Resolution__c
## Smoke Test Execution
After deployment, sample test data was created:
- Appraiser Case: AC-00001 (a0wKW000007OIiCYAW)
- 3 related deficiency records verified
### Test Payload Query (run in Apex Execute or Query Editor)
```soql
SELECT Id, Name, Appraiser_Field_Review_Date__c, Property_Address__c,
(SELECT Id, Deficiency_Number__c, Description__c, Resolution__c
FROM Deficiencies__r ORDER BY Deficiency_Number__c)
FROM Appraiser_Case__c
WHERE Id='a0wKW000007OIiCYAW'
```
### Test Payload Generation (Apex)
```apex
String caseId = 'a0wKW000007OIiCYAW';
Map<String, Object> payload = AppraiserCasePayloadBuilder.buildPayload(caseId);
System.debug(JSON.serializePretty(payload));
```
## CLM Doc Gen Integration
See [CLM_INTEGRATION.md](CLM_INTEGRATION.md) for complete setup and integration patterns.
---
_Last updated: 2026-02-26 13:26 PM_

34
docs/NEXT_STEPS_DOCGEN.md Normal file
View File

@ -0,0 +1,34 @@
# Next Steps — DocuSign CLM Launch Path
I added a placeholder Quick Action metadata file so there is an explicit place in the project for the document generation launch pattern.
## Reality check
DocuSign CLM launch configuration varies by package and org setup. Because of that, the action in this repo is a scaffold/placeholder, not a guaranteed final production action.
## Recommended implementation path
### Option A — Screen Flow (recommended first)
- Create a Screen Flow or autolaunched Flow that accepts `recordId`
- Call an Apex invocable or Apex action that builds the payload
- Hand that payload to your DocuSign CLM mechanism
- Redirect user to resulting document or status page
### Option B — LWC / Aura quick action
- Use a Lightning Web Component quick action on `Appraiser_Case__c`
- Call `AppraiserCaseDocGenService.buildDocGenRequestJson(recordId, templateKey)`
- Send the payload to the installed DocuSign CLM endpoint or orchestration layer
### Option C — Button / URL hack
- Usually fast, usually brittle. I dont recommend it unless your CLM package explicitly documents it.
## What to confirm in your org
1. Exact DocuSign CLM package/API available in Salesforce
2. Whether generation is initiated by package component, Flow action, Apex callout, or named credential call
3. Template identifier format (`templateKey`, template Id, or external document key)
4. Returned artifact behavior (attach to record, email, save to CLM repository, etc.)
## Good next move
Once you know the exact DocuSign package artifact available in the org, I can wire the placeholder into a real Flow/LWC/Apex launch path.

85
docs/SALESFORCE_SETUP.md Normal file
View File

@ -0,0 +1,85 @@
# Salesforce Setup — Appraiser Case + DocuSign CLM
## What was added
### Custom object: `Appraiser_Case__c`
- Auto-number name field labeled **Appraiser Case Number**
- `Appraiser_Field_Review_Date__c` (Date)
- `Property_Street__c` (Text 255)
- `Property_City__c` (Text 80)
- `Property_State_Province__c` (Text 80)
- `Property_Postal_Code__c` (Text 20)
- `Property_Country__c` (Text 80)
### Child custom object: `Appraiser_Deficiency__c`
- Master-detail to `Appraiser_Case__c`
- `Deficiency_Number__c` (Text 50)
- `Description__c` (Long Text Area)
- `Resolution__c` (Long Text Area)
- `Sort_Order__c` (Number)
### Layouts
- Basic page layout for Appraiser Case
- Basic page layout for Appraiser Deficiency
- Related list on Appraiser Case for deficiencies
- Basic list view on Appraiser Case
### Tabs and permissions
- Custom tabs for both objects
- Permission set: `Appraiser_Case_Admin`
### Apex
- `AppraiserCaseDocGenService.cls`
- `AppraiserCaseDocGenServiceTest.cls`
### Sample data
- Anonymous Apex script: `scripts/apex/createSampleAppraiserCase.apex`
## Deploy
From the project root:
```bash
sf project deploy start --source-dir force-app
```
Or from elsewhere:
```bash
sf project deploy start --source-dir /home/paulh/.openclaw/workspace/projects/salesforce-appraiser-review-letter/force-app
```
## Test Apex
```bash
sf apex run test --tests AppraiserCaseDocGenServiceTest --result-format human
```
## Load sample data
```bash
sf apex run --file scripts/apex/createSampleAppraiserCase.apex
```
## Assign permission set
```bash
sf org assign permset --name Appraiser_Case_Admin
```
## Suggested next steps in Salesforce
1. Deploy metadata.
2. Assign permission set.
3. Run the sample Apex script.
4. Open the `Appraiser Case` tab and verify the record + related deficiencies.
5. Validate the JSON payload in debug logs or by running the Apex methods directly.
6. Wire the DocuSign CLM launch path based on the exact package capability in your org.
## About the quick action
A placeholder quick action metadata file was added to mark the launch point, but DocuSign CLM launch mechanics vary by package/org. See `docs/NEXT_STEPS_DOCGEN.md` for the practical wiring options.
## About “page layout” and “default setup”
You said “just do default.” In Salesforce terms that means I created the baseline metadata so records can be created and edited in a normal Lightning UI without custom polish yet.

View File

@ -42,39 +42,6 @@ Outline technical and functional requirements for Appraiser Review Letter templa
- What is the authoritative object and field schema for merge?
- Are custom field mappings needed for relationships (e.g. lookup fields)?
## Initial Authoritative Salesforce Schema
- Appraiser_Case__c (Appraiser Case)
- Name (label: Appraiser Case Number, AutoNumber)
- Appraiser_Field_Review_Date__c (Date)
- Property_Address__c (Text)
- Appraiser_Case_Deficiency__c (Appraiser Case Deficiency)
- Appraiser_Case__c (Master-Detail -> Appraiser_Case__c)
- Deficiency_Number__c (Number)
- Description__c (Long Text Area)
- Resolution__c (Long Text Area)
This schema supports CLM array merges by iterating child deficiency records tied to one appraiser case.
## CLM Integration
### Payload Structure (from AppraiserCasePayloadBuilder)
The Apex class `AppraiserCasePayloadBuilder` transforms Salesforce records into CLM-ready JSON:
- AppraiserCaseNumber (string) -> Appraiser_Case__c.Name
- AppraiserFieldReviewDate (ISO date) -> Appraiser_Case__c.Appraiser_Field_Review_Date__c
- PropertyAddress (string) -> Appraiser_Case__c.Property_Address__c
- DeficiencyList (array of objects):
- deficiencyNumber (number) -> Appraiser_Case_Deficiency__c.Deficiency_Number__c
- description (string) -> Appraiser_Case_Deficiency__c.Description__c
- resolution (string) -> Appraiser_Case_Deficiency__c.Resolution__c
### CLM API Integration (CLMDocGenCallout)
- HTTP POST to DocuSign CLM API with merge payload
- Named Credentials: Securely store CLM endpoint + OAuth token
- Remote Site Settings: Whitelist CLM instance domain
- Response includes document URL and ID for tracking
See [CLM_INTEGRATION.md](CLM_INTEGRATION.md) for setup and usage patterns.
---
_Last updated: 2026-02-26 10:35 AM_
_Work in progress: Integration and schema expansion next._
---
_Last updated: 2026-02-26 10:35 AM_
_Work in progress: Integration and schema expansion next._

View File

@ -0,0 +1,103 @@
public with sharing class AppraiserCaseDocGenService {
public class DeficiencyDTO {
@AuraEnabled public String deficiencyNumber;
@AuraEnabled public String description;
@AuraEnabled public String resolution;
@AuraEnabled public Decimal sortOrder;
}
public class AppraiserCasePayload {
@AuraEnabled public Id caseId;
@AuraEnabled public String appraiserCaseNumber;
@AuraEnabled public Date appraiserFieldReviewDate;
@AuraEnabled public String propertyStreet;
@AuraEnabled public String propertyCity;
@AuraEnabled public String propertyStateProvince;
@AuraEnabled public String propertyPostalCode;
@AuraEnabled public String propertyCountry;
@AuraEnabled public String propertyAddressSingleLine;
@AuraEnabled public List<DeficiencyDTO> deficiencies;
}
@AuraEnabled(cacheable=false)
public static String buildPayloadJson(Id appraiserCaseId) {
return JSON.serialize(buildPayload(appraiserCaseId));
}
public static AppraiserCasePayload buildPayload(Id appraiserCaseId) {
Appraiser_Case__c appraiserCase = [
SELECT Id,
Name,
Appraiser_Field_Review_Date__c,
Property_Street__c,
Property_City__c,
Property_State_Province__c,
Property_Postal_Code__c,
Property_Country__c,
(SELECT Id,
Name,
Deficiency_Number__c,
Description__c,
Resolution__c,
Sort_Order__c
FROM Appraiser_Deficiencies__r
ORDER BY Sort_Order__c ASC, CreatedDate ASC)
FROM Appraiser_Case__c
WHERE Id = :appraiserCaseId
LIMIT 1
];
AppraiserCasePayload payload = new AppraiserCasePayload();
payload.caseId = appraiserCase.Id;
payload.appraiserCaseNumber = appraiserCase.Name;
payload.appraiserFieldReviewDate = appraiserCase.Appraiser_Field_Review_Date__c;
payload.propertyStreet = appraiserCase.Property_Street__c;
payload.propertyCity = appraiserCase.Property_City__c;
payload.propertyStateProvince = appraiserCase.Property_State_Province__c;
payload.propertyPostalCode = appraiserCase.Property_Postal_Code__c;
payload.propertyCountry = appraiserCase.Property_Country__c;
payload.propertyAddressSingleLine = buildAddress(appraiserCase);
payload.deficiencies = new List<DeficiencyDTO>();
for (Appraiser_Deficiency__c deficiency : appraiserCase.Appraiser_Deficiencies__r) {
DeficiencyDTO dto = new DeficiencyDTO();
dto.deficiencyNumber = deficiency.Deficiency_Number__c;
dto.description = deficiency.Description__c;
dto.resolution = deficiency.Resolution__c;
dto.sortOrder = deficiency.Sort_Order__c;
payload.deficiencies.add(dto);
}
return payload;
}
private static String buildAddress(Appraiser_Case__c appraiserCase) {
List<String> parts = new List<String>();
if (String.isNotBlank(appraiserCase.Property_Street__c)) parts.add(appraiserCase.Property_Street__c);
List<String> cityLine = new List<String>();
if (String.isNotBlank(appraiserCase.Property_City__c)) cityLine.add(appraiserCase.Property_City__c);
if (String.isNotBlank(appraiserCase.Property_State_Province__c)) cityLine.add(appraiserCase.Property_State_Province__c);
if (String.isNotBlank(appraiserCase.Property_Postal_Code__c)) cityLine.add(appraiserCase.Property_Postal_Code__c);
if (!cityLine.isEmpty()) parts.add(String.join(cityLine, ', '));
if (String.isNotBlank(appraiserCase.Property_Country__c)) parts.add(appraiserCase.Property_Country__c);
return String.join(parts, ' | ');
}
@AuraEnabled(cacheable=false)
public static Map<String, Object> buildDocGenRequest(Id appraiserCaseId, String templateKey) {
AppraiserCasePayload payload = buildPayload(appraiserCaseId);
Map<String, Object> requestBody = new Map<String, Object>();
requestBody.put('templateKey', templateKey);
requestBody.put('recordId', appraiserCaseId);
requestBody.put('sourceObject', 'Appraiser_Case__c');
requestBody.put('mergeData', payload);
return requestBody;
}
@AuraEnabled(cacheable=false)
public static String buildDocGenRequestJson(Id appraiserCaseId, String templateKey) {
return JSON.serialize(buildDocGenRequest(appraiserCaseId, templateKey));
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>63.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -0,0 +1,36 @@
@IsTest
private class AppraiserCaseDocGenServiceTest {
@IsTest
static void buildsPayloadAndRequestJson() {
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
Appraiser_Field_Review_Date__c = Date.newInstance(2026, 4, 1),
Property_Street__c = '123 Main St',
Property_City__c = 'Ottawa',
Property_State_Province__c = 'ON',
Property_Postal_Code__c = 'K1A 0A1',
Property_Country__c = 'Canada'
);
insert appraiserCase;
insert new Appraiser_Deficiency__c(
Name = 'Deficiency 1',
Appraiser_Case__c = appraiserCase.Id,
Deficiency_Number__c = '1',
Description__c = 'Missing comparable sale analysis',
Resolution__c = 'Provide updated comparable sales section',
Sort_Order__c = 1
);
Test.startTest();
AppraiserCaseDocGenService.AppraiserCasePayload payload = AppraiserCaseDocGenService.buildPayload(appraiserCase.Id);
String json = AppraiserCaseDocGenService.buildDocGenRequestJson(appraiserCase.Id, 'APPRAISER_REVIEW_LETTER');
Test.stopTest();
System.assertEquals(appraiserCase.Id, payload.caseId);
System.assertEquals('Ottawa', payload.propertyCity);
System.assertEquals(1, payload.deficiencies.size());
System.assertEquals('1', payload.deficiencies[0].deficiencyNumber);
System.assert(json.contains('APPRAISER_REVIEW_LETTER'));
System.assert(json.contains('Missing comparable sale analysis'));
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>63.0</apiVersion>
<status>Active</status>
</ApexClass>

View File

@ -112,20 +112,25 @@ public class CLMDocGenCallout {
private static String buildDataXml(Map<String, Object> payload) {
String xml = '<TemplateFieldData>';
// Emit flat fields first
for (String key : payload.keySet()) {
if (key == 'DeficiencyList') continue;
xml += '<' + key + '>' + escapeXml(String.valueOf(payload.get(key))) + '</' + key + '>';
}
// Emit DeficiencyList as a nested list so templates can iterate dynamically
List<Object> deficiencies = (List<Object>) payload.get('DeficiencyList');
if (deficiencies != null) {
if (deficiencies != null && !deficiencies.isEmpty()) {
xml += '<DeficiencyList>';
for (Integer i = 0; i < deficiencies.size(); i++) {
Map<String, Object> d = (Map<String, Object>) deficiencies[i];
String p = 'Deficiency_' + (i + 1) + '_';
xml += '<' + p + 'Number>' + escapeXml(String.valueOf(d.get('deficiencyNumber'))) + '</' + p + 'Number>';
xml += '<' + p + 'Description>' + escapeXml(String.valueOf(d.get('description'))) + '</' + p + 'Description>';
xml += '<' + p + 'Resolution>' + escapeXml(String.valueOf(d.get('resolution'))) + '</' + p + 'Resolution>';
xml += '<Deficiency>';
xml += '<Number>' + escapeXml(String.valueOf(d.get('deficiencyNumber'))) + '</Number>';
xml += '<Description>' + escapeXml(String.valueOf(d.get('description'))) + '</Description>';
xml += '<Resolution>' + escapeXml(String.valueOf(d.get('resolution'))) + '</Resolution>';
xml += '</Deficiency>';
}
xml += '</DeficiencyList>';
xml += '<DeficiencyCount>' + deficiencies.size() + '</DeficiencyCount>';
}

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>Information</label>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>Name</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>Appraiser_Field_Review_Date__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>CreatedDate</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>LastModifiedDate</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>Property Address</label>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>Property_Street__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>Property_City__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>Property_State_Province__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>Property_Postal_Code__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>Property_Country__c</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<relatedLists>
<fields>NAME</fields>
<fields>Deficiency_Number__c</fields>
<fields>Description__c</fields>
<fields>Resolution__c</fields>
<fields>Sort_Order__c</fields>
<relatedList>Appraiser_Deficiencies__r</relatedList>
</relatedLists>
<relatedLists>
<fields>SUBJECT</fields>
<fields>STATUS</fields>
<fields>DUE_DATE</fields>
<relatedList>OpenActivities</relatedList>
</relatedLists>
<showEmailCheckbox>false</showEmailCheckbox>
<showHighlightsPanel>true</showHighlightsPanel>
<showInteractionLogPanel>false</showInteractionLogPanel>
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
</Layout>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<Layout xmlns="http://soap.sforce.com/2006/04/metadata">
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>Information</label>
<layoutColumns>
<layoutItems>
<behavior>Required</behavior>
<field>Name</field>
</layoutItems>
<layoutItems>
<behavior>Required</behavior>
<field>Appraiser_Case__c</field>
</layoutItems>
<layoutItems>
<behavior>Edit</behavior>
<field>Deficiency_Number__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>Sort_Order__c</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<layoutSections>
<customLabel>false</customLabel>
<detailHeading>true</detailHeading>
<editHeading>true</editHeading>
<label>Details</label>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>Description__c</field>
</layoutItems>
</layoutColumns>
<layoutColumns>
<layoutItems>
<behavior>Edit</behavior>
<field>Resolution__c</field>
</layoutItems>
</layoutColumns>
<style>TwoColumnsTopToBottom</style>
</layoutSections>
<showEmailCheckbox>false</showEmailCheckbox>
<showHighlightsPanel>true</showHighlightsPanel>
<showInteractionLogPanel>false</showInteractionLogPanel>
<showRunAssignmentRulesCheckbox>false</showRunAssignmentRulesCheckbox>
<showSubmitAndAttachButton>false</showSubmitAndAttachButton>
</Layout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
<allowMergeFieldsInBody>false</allowMergeFieldsInBody>
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
<calloutStatus>Enabled</calloutStatus>
<generateAuthorizationHeader>true</generateAuthorizationHeader>
<label>CLMuatDownload</label>
<namedCredentialParameters>
<parameterName>Url</parameterName>
<parameterType>Url</parameterType>
<parameterValue>https://apidownloaduatna11.springcm.com</parameterValue>
</namedCredentialParameters>
<namedCredentialParameters>
<externalCredential>DocusignJWT</externalCredential>
<parameterName>ExternalCredential</parameterName>
<parameterType>Authentication</parameterType>
</namedCredentialParameters>
<namedCredentialType>SecuredEndpoint</namedCredentialType>
</NamedCredential>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<NamedCredential xmlns="http://soap.sforce.com/2006/04/metadata">
<allowMergeFieldsInBody>false</allowMergeFieldsInBody>
<allowMergeFieldsInHeader>false</allowMergeFieldsInHeader>
<calloutStatus>Enabled</calloutStatus>
<generateAuthorizationHeader>true</generateAuthorizationHeader>
<label>CLMuatDownloadNamedCreds</label>
<namedCredentialParameters>
<parameterName>Url</parameterName>
<parameterType>Url</parameterType>
<parameterValue>https://apidownloaduatna11.springcm.com</parameterValue>
</namedCredentialParameters>
<namedCredentialParameters>
<externalCredential>DocusignJWT</externalCredential>
<parameterName>ExternalCredential</parameterName>
<parameterType>Authentication</parameterType>
</namedCredentialParameters>
<namedCredentialType>SecuredEndpoint</namedCredentialType>
</NamedCredential>

View File

@ -1,16 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<actionOverrides>
<actionName>New</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<type>Default</type>
</actionOverrides>
<allowInChatterGroups>false</allowInChatterGroups>
<compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
<deploymentStatus>Deployed</deploymentStatus>
<description>Main record for appraiser review letter generation in CLM.</description>
<enableActivities>true</enableActivities>
<enableBulkApi>true</enableBulkApi>
<enableFeeds>false</enableFeeds>
<enableHistory>true</enableHistory>
<enableLicensing>false</enableLicensing>
<enableReports>true</enableReports>
<enableSearch>true</enableSearch>
<enableSharing>true</enableSharing>
<enableStreamingApi>true</enableStreamingApi>
<externalSharingModel>Private</externalSharingModel>
<label>Appraiser Case</label>
<nameField>
<displayFormat>AC-{00000}</displayFormat>
<displayFormat>AC-{000000}</displayFormat>
<label>Appraiser Case Number</label>
<type>AutoNumber</type>
</nameField>
<pluralLabel>Appraiser Cases</pluralLabel>
<searchLayouts></searchLayouts>
<sharingModel>ReadWrite</sharingModel>
<visibility>Public</visibility>
</CustomObject>

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Appraiser_Field_Review_Date__c</fullName>
<description>Date the appraiser field review was completed.</description>
<label>Appraiser Field Review Date</label>
<required>false</required>
<trackHistory>false</trackHistory>
<trackHistory>true</trackHistory>
<type>Date</type>
</CustomField>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Property_City__c</fullName>
<label>Property City</label>
<length>80</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>Text</type>
</CustomField>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Property_Country__c</fullName>
<label>Property Country</label>
<length>80</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>Text</type>
</CustomField>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Property_Postal_Code__c</fullName>
<label>Property Postal Code</label>
<length>20</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>Text</type>
</CustomField>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Property_State_Province__c</fullName>
<label>Property State/Province</label>
<length>80</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>Text</type>
</CustomField>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Property_Street__c</fullName>
<label>Property Street</label>
<length>255</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>Text</type>
</CustomField>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<ListView xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>All</fullName>
<columns>Name</columns>
<columns>Appraiser_Field_Review_Date__c</columns>
<columns>Property_Street__c</columns>
<columns>Property_City__c</columns>
<columns>Property_State_Province__c</columns>
<columns>LastModifiedDate</columns>
<filterScope>Everything</filterScope>
<label>All</label>
</ListView>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<actionOverrides>
<actionName>New</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<type>Default</type>
</actionOverrides>
<allowInChatterGroups>false</allowInChatterGroups>
<compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
<deploymentStatus>Deployed</deploymentStatus>
<enableActivities>false</enableActivities>
<enableBulkApi>true</enableBulkApi>
<enableFeeds>false</enableFeeds>
<enableHistory>true</enableHistory>
<enableLicensing>false</enableLicensing>
<enableReports>true</enableReports>
<enableSearch>true</enableSearch>
<enableSharing>true</enableSharing>
<enableStreamingApi>true</enableStreamingApi>
<externalSharingModel>ControlledByParent</externalSharingModel>
<label>Appraiser Deficiency</label>
<nameField>
<label>Appraiser Deficiency Name</label>
<type>Text</type>
</nameField>
<pluralLabel>Appraiser Deficiencies</pluralLabel>
<searchLayouts></searchLayouts>
<sharingModel>ControlledByParent</sharingModel>
<visibility>Public</visibility>
</CustomObject>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Appraiser_Case__c</fullName>
<label>Appraiser Case</label>
<referenceTo>Appraiser_Case__c</referenceTo>
<relationshipLabel>Appraiser Deficiencies</relationshipLabel>
<relationshipName>Appraiser_Deficiencies</relationshipName>
<reparentableMasterDetail>false</reparentableMasterDetail>
<required>true</required>
<trackHistory>false</trackHistory>
<type>MasterDetail</type>
<writeRequiresMasterRead>false</writeRequiresMasterRead>
</CustomField>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Deficiency_Number__c</fullName>
<label>Deficiency Number</label>
<length>50</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>Text</type>
</CustomField>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Description__c</fullName>
<label>Description</label>
<length>32768</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>LongTextArea</type>
<visibleLines>5</visibleLines>
</CustomField>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Resolution__c</fullName>
<label>Resolution</label>
<length>32768</length>
<required>false</required>
<trackHistory>true</trackHistory>
<type>LongTextArea</type>
<visibleLines>5</visibleLines>
</CustomField>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Sort_Order__c</fullName>
<label>Sort Order</label>
<precision>6</precision>
<required>false</required>
<scale>0</scale>
<trackHistory>true</trackHistory>
<type>Number</type>
</CustomField>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Access to Appraiser Case and Appraiser Deficiency objects for setup and early testing.</description>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Case__c.Appraiser_Field_Review_Date__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Case__c.Property_Street__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Case__c.Property_City__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Case__c.Property_State_Province__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Case__c.Property_Postal_Code__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Case__c.Property_Country__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Deficiency__c.Appraiser_Case__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Deficiency__c.Deficiency_Number__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Deficiency__c.Description__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Deficiency__c.Resolution__c</field>
<readable>true</readable>
</fieldPermissions>
<fieldPermissions>
<editable>true</editable>
<field>Appraiser_Deficiency__c.Sort_Order__c</field>
<readable>true</readable>
</fieldPermissions>
<hasActivationRequired>false</hasActivationRequired>
<label>Appraiser Case Admin</label>
<objectPermissions>
<allowCreate>true</allowCreate>
<allowDelete>true</allowDelete>
<allowEdit>true</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>true</modifyAllRecords>
<object>Appraiser_Case__c</object>
<viewAllRecords>true</viewAllRecords>
</objectPermissions>
<objectPermissions>
<allowCreate>true</allowCreate>
<allowDelete>true</allowDelete>
<allowEdit>true</allowEdit>
<allowRead>true</allowRead>
<modifyAllRecords>true</modifyAllRecords>
<object>Appraiser_Deficiency__c</object>
<viewAllRecords>true</viewAllRecords>
</objectPermissions>
<tabSettings>
<tab>Appraiser_Case__c</tab>
<visibility>Visible</visibility>
</tabSettings>
<tabSettings>
<tab>Appraiser_Deficiency__c</tab>
<visibility>Visible</visibility>
</tabSettings>
</PermissionSet>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<QuickAction xmlns="http://soap.sforce.com/2006/04/metadata">
<description>Placeholder action to launch Appraiser review letter generation flow or component.</description>
<height>600</height>
<label>Generate Review Letter</label>
<optionsCreateFeedItem>false</optionsCreateFeedItem>
<standardLabel>New</standardLabel>
<targetObject>Appraiser_Case__c</targetObject>
<type>Create</type>
<width>800</width>
</QuickAction>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom67: Gears</motif>
</CustomTab>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomTab xmlns="http://soap.sforce.com/2006/04/metadata">
<customObject>true</customObject>
<motif>Custom18: Form</motif>
</CustomTab>

View File

@ -0,0 +1,32 @@
Appraiser_Case__c appraiserCase = new Appraiser_Case__c(
Appraiser_Field_Review_Date__c = Date.today(),
Property_Street__c = '123 Main St',
Property_City__c = 'Ottawa',
Property_State_Province__c = 'ON',
Property_Postal_Code__c = 'K1A 0A1',
Property_Country__c = 'Canada'
);
insert appraiserCase;
insert new List<Appraiser_Deficiency__c>{
new Appraiser_Deficiency__c(
Name = 'Deficiency 1',
Appraiser_Case__c = appraiserCase.Id,
Deficiency_Number__c = '1',
Description__c = 'Missing comparable sale analysis',
Resolution__c = 'Provide updated comparable sales section',
Sort_Order__c = 1
),
new Appraiser_Deficiency__c(
Name = 'Deficiency 2',
Appraiser_Case__c = appraiserCase.Id,
Deficiency_Number__c = '2',
Description__c = 'Exterior condition comments are too brief',
Resolution__c = 'Expand comments and attach photos',
Sort_Order__c = 2
)
};
System.debug('Created Appraiser Case Id: ' + appraiserCase.Id);
System.debug('Payload JSON: ' + AppraiserCaseDocGenService.buildPayloadJson(appraiserCase.Id));
System.debug('Doc Gen JSON: ' + AppraiserCaseDocGenService.buildDocGenRequestJson(appraiserCase.Id, 'APPRAISER_REVIEW_LETTER'));

View File

@ -0,0 +1,99 @@
#!/usr/bin/env python3
import json, difflib
postman_json_text = '''{
"TemplateDocument": {
"Href": "https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/documents/a0cbc0e6-d87d-459e-8d63-66baa47878f3" },
"DataXML": "<TemplateFieldData><AppraiserCaseNumber>AC-00001</AppraiserCaseNumber><AppraiserFieldReviewDate>02/04/2026</AppraiserFieldReviewDate><PropertyAddress>123 Main St, Denver, CO 80202</PropertyAddress><DeficiencyList><Deficiency><Number>1</Number><Description>Missing comparable sale adjustment detail.</Description><Resolution>Added adjustment rationale and supporting calculations.</Resolution></Deficiency><Deficiency><Number>2</Number><Description>Neighborhood trend explanation insufficient.</Description><Resolution>Expanded market trend narrative with MLS evidence.</Resolution></Deficiency><Deficiency><Number>3</Number><Description>Photo date stamps were not included.</Description><Resolution>Re-uploaded photos with date metadata and captions.</Resolution></Deficiency></DeficiencyList><DeficiencyCount>3</DeficiencyCount></TemplateFieldData>",
"DestinationDocumentName": "Review_AC-00001.docx",
"DestinationFolder": {
"Href": "https://apiuatna11.springcm.com/v2/bccae332-c7db-4892-ab85-257df0f70fea/folders/12220442-b12e-f111-84fc-88e9a4bd0d9c"
}
}
'''
# Parse the Postman JSON
postman = json.loads(postman_json_text)
# Reconstruct the Apex payload (simulate AppraiserCasePayloadBuilder output)
payload = {
'AppraiserCaseNumber': 'AC-00001',
'AppraiserFieldReviewDate': '02/04/2026',
'PropertyAddress': '123 Main St, Denver, CO 80202',
'DeficiencyList': [
{'deficiencyNumber': '1', 'description': 'Missing comparable sale adjustment detail.', 'resolution': 'Added adjustment rationale and supporting calculations.'},
{'deficiencyNumber': '2', 'description': 'Neighborhood trend explanation insufficient.', 'resolution': 'Expanded market trend narrative with MLS evidence.'},
{'deficiencyNumber': '3', 'description': 'Photo date stamps were not included.', 'resolution': 'Re-uploaded photos with date metadata and captions.'}
]
}
# Function to mimic buildDataXml from Apex implementation
def escape_xml(s):
if s is None:
return ''
return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;').replace("'","&apos;")
def build_data_xml(payload):
xml = '<TemplateFieldData>'
# Emit flat fields first (Apex iterates payload.keySet() which is not ordered in Python dict, but we'll follow this order)
# We'll follow the same ordering as in Postman: AppraiserCaseNumber, AppraiserFieldReviewDate, PropertyAddress
for key in ['AppraiserCaseNumber','AppraiserFieldReviewDate','PropertyAddress']:
v = payload.get(key)
xml += f'<{key}>' + escape_xml(str(v) if v is not None else '') + f'</{key}>'
# Emit DeficiencyList nested structure
deficiencies = payload.get('DeficiencyList')
if deficiencies:
xml += '<DeficiencyList>'
for d in deficiencies:
xml += '<Deficiency>'
xml += '<Number>' + escape_xml(str(d.get('deficiencyNumber'))) + '</Number>'
xml += '<Description>' + escape_xml(str(d.get('description'))) + '</Description>'
xml += '<Resolution>' + escape_xml(str(d.get('resolution'))) + '</Resolution>'
xml += '</Deficiency>'
xml += '</DeficiencyList>'
xml += '<DeficiencyCount>' + str(len(deficiencies)) + '</DeficiencyCount>'
xml += '</TemplateFieldData>'
return xml
apex_data_xml = build_data_xml(payload)
apex_request = {
'TemplateDocument': {'Href': postman['TemplateDocument']['Href']},
'DataXML': apex_data_xml,
'DestinationDocumentName': postman['DestinationDocumentName'],
'DestinationFolder': {'Href': postman['DestinationFolder']['Href']}
}
# Compare postman vs apex
print('Comparing top-level fields:')
for key in ['TemplateDocument','DestinationDocumentName','DestinationFolder']:
p = postman.get(key)
a = apex_request.get(key)
equal = (p == a)
print(f' - {key}:', 'MATCH' if equal else 'DIFFER')
# Compare DataXML
postman_xml = postman['DataXML']
apex_xml = apex_request['DataXML']
if postman_xml == apex_xml:
print('\nDataXML: EXACT MATCH')
else:
print('\nDataXML: DIFFER')
# show diff
pd = postman_xml.split('><')
ad = apex_xml.split('><')
diff = difflib.unified_diff(pd, ad, fromfile='postman_xml', tofile='apex_xml', lineterm='')
print('\n'.join(diff))
# also show small head/tail
print('\n-- Postman head 200 chars --\n', postman_xml[:200])
print('\n-- Apex head 200 chars --\n', apex_xml[:200])
# Additionally print whether other top-level objects match (TemplateDocument Href and Folder Href)
print('\nTemplateDocument.Href match:', postman['TemplateDocument']['Href'] == apex_request['TemplateDocument']['Href'])
print('DestinationFolder.Href match:', postman['DestinationFolder']['Href'] == apex_request['DestinationFolder']['Href'])
# Print apex_data_xml for inspection
print('\n--- Apex DataXML ---\n')
print(apex_data_xml)

View File

@ -0,0 +1,100 @@
#!/usr/bin/env python3
import os, re, base64, zipfile, xml.etree.ElementTree as ET, sys
log_path = '/home/paulh/.vscode-server/data/User/workspaceStorage/79b924110cb5ff6de49811d445e59969-1/GitHub.copilot-chat/chat-session-resources/90e6dae0-2184-412b-af0b-eac258be98c5/call_gY8uUyzuGvFiN46d4ZPVFtjz__vscode-1775271381281/content.txt'
out_dir = 'artifacts/doc_inspect'
os.makedirs(out_dir, exist_ok=True)
start=False
chunks = []
with open(log_path, 'r', errors='replace') as f:
for line in f:
if 'BASE64_BEGIN' in line:
start = True
continue
if 'BASE64_END' in line:
break
if start:
if 'BASE64_CHUNK:' in line:
parts = line.split('BASE64_CHUNK:',1)[1].strip()
chunks.append(parts)
else:
# fallback: if line looks like base64 (long and only base64 chars + =), take it
s = line.strip()
if len(s) > 100 and re.fullmatch(r'[A-Za-z0-9+/=\n\r]+', s):
chunks.append(s)
if not chunks:
print('ERROR: no base64 chunks found in log at', log_path)
sys.exit(2)
b64 = ''.join(chunks)
# sanitize (remove any DEBUG prefixes that snuck in)
b64 = re.sub(r'\s+', '', b64)
try:
data = base64.b64decode(b64)
except Exception as e:
print('ERROR decoding base64:', e)
sys.exit(3)
docx_path = os.path.join(out_dir, 'downloaded.docx')
with open(docx_path, 'wb') as f:
f.write(data)
print('WROTE_DOCX:', docx_path, 'size=', os.path.getsize(docx_path))
# unzip
unzip_dir = os.path.join(out_dir, 'unzipped')
os.makedirs(unzip_dir, exist_ok=True)
try:
with zipfile.ZipFile(docx_path, 'r') as z:
z.extractall(unzip_dir)
except Exception as e:
print('ERROR unzipping docx:', e)
sys.exit(4)
doc_xml = os.path.join(unzip_dir, 'word', 'document.xml')
if not os.path.exists(doc_xml):
print('ERROR: word/document.xml not found in the docx')
sys.exit(5)
# parse XML and extract tables
ns = {'w':'http://schemas.openxmlformats.org/wordprocessingml/2006/main'}
ET.register_namespace('w', ns['w'])
try:
tree = ET.parse(doc_xml)
root = tree.getroot()
except Exception as e:
print('ERROR parsing document.xml:', e)
sys.exit(6)
tables = root.findall('.//w:tbl', ns)
print('TABLE_COUNT:', len(tables))
# For each table, collect row texts (limit output to first 5 tables and 20 rows each)
found_def_texts = []
for ti, tbl in enumerate(tables[:5], start=1):
rows = tbl.findall('.//w:tr', ns)
print('\n--- TABLE', ti, 'rows=', len(rows), '---')
for ri, tr in enumerate(rows[:20], start=1):
texts = [t.text for t in tr.findall('.//w:t', ns) if t.text]
joined = ' | '.join(texts).strip()
if joined:
print('ROW %d:'%ri, repr(joined))
# heuristic: look for keywords
if any(k.lower() in joined.lower() for k in ('deficiency','description','defect','ac-','AC-','DeficiencyList')):
found_def_texts.append(joined)
else:
print('ROW %d: <empty>'%ri)
# Also search whole document.xml for certain keywords
full_xml = open(doc_xml,'r',encoding='utf-8',errors='replace').read()
keywords = ['Deficiency','DeficiencyList','Description','<TableRow','AC-']
hits = {k: (full_xml.count(k)) for k in keywords}
print('\nKEYWORD_COUNTS:')
for k,v in hits.items():
print(k+':', v)
print('\nFOUND_DEFICIENCY_TEXTS_COUNT:', len(found_def_texts))
for i, txt in enumerate(found_def_texts[:20], start=1):
print('FOUND_%d:'%i, txt)
# Exit with success
print('\nSUMMARY: docx_size=%d tables=%d deficiency_text_found=%s' % (os.path.getsize(docx_path), len(tables), bool(found_def_texts)))
print('OUTPUT_DIR:', os.path.abspath(out_dir))

View File

@ -8,5 +8,5 @@
"name": "salesforce-appraiser-review-letter",
"namespace": "",
"sfdcLoginUrl": "https://login.salesforce.com",
"sourceApiVersion": "62.0"
"sourceApiVersion": "63.0"
}