Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,14 @@ dm comps --body '{

Output includes subject property details, value estimation with confidence interval, summary statistics (average/median price, price per sqft), and a table of comparable properties.

Use `criteria.min_year_built` and `criteria.max_year_built` in a JSON body or file to limit comps by year built:

```bash
dm comps --body '{"property_ids":["prop_12345"],"criteria":{"min_year_built":1980,"max_year_built":2000}}' --json
```

Both bounds are inclusive whole years from 1 through 9999. Either bound can stand alone; the minimum must not exceed the maximum. Omit both for no year filter. A specified bound excludes comps with an unknown year built.

---

### Lists Commands
Expand Down
3 changes: 3 additions & 0 deletions playbook/PLAYBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,9 @@ dm comps prop_12345 --include-foreclosures

# Full control via JSON body
dm comps --body '{"property_ids": ["prop_12345"], "location": {"type": "radius", "radius_miles": 2}, "criteria": {"timeframe": "12months", "limit": 50}}'

# Inclusive year-built range; either bound may be omitted
dm comps --body '{"property_ids":["prop_12345"],"criteria":{"min_year_built":1980,"max_year_built":2000}}' --json
```

### People
Expand Down
7 changes: 4 additions & 3 deletions scripts/smoke-live.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { promisify } from 'node:util';
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';

// An explicit target and QA key are required. Never fall back to a saved login.
const apiKey = process.env.DM_API_KEY;
Expand All @@ -26,6 +26,7 @@ try {
const isolatedHome = join(temporary, 'home');
await mkdir(isolatedHome, { mode: 0o700 });
const isolation = join(temporary, 'isolation.mjs');
const isolationUrl = pathToFileURL(isolation).href;
await writeFile(isolation, [
"import os from 'node:os';",
"import childProcess from 'node:child_process';",
Expand All @@ -34,11 +35,11 @@ try {
"childProcess.execFileSync = () => { throw new Error('Personal credential store disabled during smoke'); };",
'syncBuiltinESMExports();',
].join('\n'), { mode: 0o600 });
await execute(process.execPath, ['--import', isolation, '--input-type=module', '--eval',
await execute(process.execPath, ['--import', isolationUrl, '--input-type=module', '--eval',
`const { writeConfig } = await import(${JSON.stringify(new URL('../dist/lib/config.js', import.meta.url).href)}); writeConfig({apiKey: process.env.DM_API_KEY, keyId:'qa-smoke', organizationId:0, organizationName:'QA smoke', organizationSlug:'qa-smoke'});`,
], { cwd: temporary, env: environment, timeout: 10000 });
const command = async args => {
const { stdout } = await execute(process.execPath, ['--import', isolation, join(root, 'dist/index.js'), ...args, '--json', '--quiet'], {
const { stdout } = await execute(process.execPath, ['--import', isolationUrl, join(root, 'dist/index.js'), ...args, '--json', '--quiet'], {
cwd: temporary, env: environment, timeout: 45000, maxBuffer: 2 * 1024 * 1024,
});
return JSON.parse(stdout);
Expand Down
20 changes: 20 additions & 0 deletions tests/commands/publicApiExtensions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { drivingGet, drivingList } from '../../src/commands/driving.js';
import { prospectsList } from '../../src/commands/prospects.js';
import { listsItems } from '../../src/commands/lists.js';
import { phonesDnc } from '../../src/commands/phones.js';
import { comps } from '../../src/commands/comps.js';

beforeEach(() => {
vi.clearAllMocks();
Expand All @@ -59,6 +60,25 @@ beforeEach(() => {
});

describe('CLI public API extensions', () => {
it.each([{ body: '{}' }, { file: 'comps.json' }])(
'forwards Year Built criteria from JSON input %j',
async (input) => {
const request = {
property_ids: ['prop_123'],
criteria: { min_year_built: 1980, max_year_built: 2000 },
};
mockParseRequestBody.mockResolvedValue(request);

await comps([], { ...input, json: true });

expect(mockParseRequestBody).toHaveBeenCalledWith(expect.objectContaining(input));
expect(mockApiRequest).toHaveBeenCalledWith('/comps', {
method: 'POST',
body: request,
});
}
);

it('reads drive history and filters prospects added during drives', async () => {
mockApiRequest
.mockResolvedValueOnce({
Expand Down
Loading