docs/customization/README.md
Rama master · réplica de lectura del repositorio original.
# Customization Guide
DOMINUS is designed to be forked and customized. This guide explains how to
adapt every layer to your needs — from scoring weights to provider swaps.
## Table of Contents
1. [Scoring Engine](#scoring-engine)
2. [Providers](#providers)
3. [Trademark Matching](#trademark-matching)
4. [Pipeline Behaviour](#pipeline-behaviour)
5. [Notifications](#notifications)
6. [Deployment](#deployment)
---
## Scoring Engine
### Weights
The four signal weights (intrinsic, commercial, market, expiry) can be tuned
via a JSON override file. The default weights are:
| Signal | Default Weight |
| ---------- | -------------- |
| Intrinsic | 0.30 |
| Commercial | 0.35 |
| Market | 0.25 |
| Expiry | 0.10 |
To override, create a JSON file:
```json
{
"weights": {
"intrinsic": 0.25,
"commercial": 0.4,
"market": 0.25,
"expiry": 0.1
}
}
```
Activate it in `.env`:
```
SCORING_WEIGHTS_OVERRIDE=./data/weights-override.json
```
The `dominus backtest suggest-weights --apply` command writes this file
automatically based on backtest results. See [backtest docs](../../README.md#backtest--calibration)
for the two-gate activation process.
### Signal Thresholds
Each scoring signal uses tunable parameters. Defaults reflect the original
conservative tuning:
| Env Variable | Default | Signal | Purpose |
| ------------------------------- | --------- | ---------- | --------------------------------------------------------------------------------- |
| `SCORING_IDEAL_LENGTH` | `7` | Intrinsic | Ideal SLD character length |
| `SCORING_MAX_LENGTH` | `20` | Intrinsic | Max SLD length before score = 0 |
| `SCORING_MAX_VOLUME` | `1000000` | Commercial | Max monthly search volume ceiling |
| `SCORING_MAX_CPC` | `50` | Commercial | Max CPC ceiling |
| `SCORING_FLOOR_VALUE` | `500` | Market | Floor market value in EUR |
| `SCORING_HIGH_VALUE` | `10000` | Market | High market value in EUR |
| `SCORING_MAX_AGE_YEARS` | `20` | Expiry | Max domain age for expiry signal |
| `SCORING_MAX_BACKLINKS` | `1000` | Expiry | Max backlinks for expiry signal |
| `SCORING_MAX_WAYBACK` | `500` | Expiry | Max Wayback snapshots for expiry signal |
| `SCORING_BUY_MAX_RATIO` | `0.5` | Engine | suggestedBuyMax = expectedValue × this |
| `SCORING_LIST_PRICE_MULTIPLIER` | `2.5` | Engine | suggestedListPrice = expectedValue × this |
| `SCORING_BASE_MARKET_VALUE` | `500` | Engine | Base EUR value for expected value calc |
| `SCORING_CONFIDENCE_BASE` | `0.2` | Engine | Starting confidence with 1 signal |
| `SCORING_CONFIDENCE_CAP` | `0.8` | Engine | Absolute confidence cap |
| `BUY_MAX_ABSOLUTE_CAP` | `0` | Engine | Absolute cap on suggestedBuyMax (EUR); 0 = uncapped (evidence-anchored, ADR-0055) |
### TLD Bonuses
TLD bonuses are multipliers applied to the intrinsic signal. Defaults:
| TLD | Bonus |
| --- | --- |
| `.com` | 1.0 |
| `.ai` | 0.9 |
| `.io` | 0.85 |
| `.co` | 0.75 |
| `.net` | 0.65 |
| `.org` | 0.55 |
| `.app`, `.dev`, `.tv` | 0.5 |
| `.me`, `.us`, `.tech`, `.xyz` | 0.45 |
| `.shop`, `.site`, `.online`, `.store` | 0.4 |
| **any other TLD** | 0.45 (`UNKNOWN_TLD_MULTIPLIER`) |
The full list lives in `src/scoring/weights.ts`; unknown TLDs are penalised
relative to `.com` rather than dropped to zero.
Override via a JSON file:
```json
{
".com": 1.0,
".io": 0.9,
".xyz": 0.4,
".dev": 0.7
}
```
Set in `.env`:
```
TLD_BONUSES_PATH=./data/tld-bonuses.json
```
The file is merged with defaults — only the TLDs you specify are overridden.
### Adding a Custom Signal
To add a fifth signal (e.g. social media presence, PageRank, domain authority):
1. Create `src/scoring/signals/social-signal.ts`:
```typescript
import type { SignalOutput, ScoringInput } from '../../types/score.js';
export async function computeSocialScore(
input: ScoringInput,
provider: YourProvider,
weight: number,
): Promise<SignalOutput> {
// Your scoring logic here
return { score, weight, details: {} };
}
```
2. Wire it in `src/scoring/scoring-engine.ts`:
```typescript
const social = await computeSocialScore(input, this.socialProvider, this.weights.social);
```
3. Add `social: number` to `ScoringWeights` in `src/scoring/weights.ts`.
4. Add the new weight to your `SCORING_WEIGHTS_OVERRIDE` JSON.
---
## Providers
### Swapping a Provider
Every provider is behind a TypeScript interface. To swap one:
1. Create a new implementation file (e.g. `src/providers/keyword/google-ads-provider.ts`)
2. Implement the interface:
```typescript
import type { KeywordProvider, KeywordMetrics } from './keyword-provider.js';
export class GoogleAdsProvider implements KeywordProvider {
async getMetrics(term: string): Promise<KeywordMetrics> {
// Call the Google Ads API here
return { term, monthlySearchVolume: 5000, cpc: 3.5, competition: 0.6 };
}
}
```
3. Swap the instance in `src/app/composition-root.ts`:
```typescript
// Before:
const keywordProvider = new ManualKeywordProvider(config.KEYWORD_DATA_PATH);
// After:
const keywordProvider = new GoogleAdsProvider(/* your config */);
```
That's it. The scoring engine, pipeline, and all consumers continue working
without modification.
### Available Provider Interfaces
| Interface | File | Methods |
| ------------------- | ----------------------------------------------- | --------------------------------------------------------- |
| `DnsProvider` | `src/providers/dns/dns-provider.ts` | `checkAvailability`, `checkBulk` |
| `RdapProvider` | `src/providers/rdap/rdap-provider.ts` | `confirm` |
| `TrademarkProvider` | `src/providers/trademark/trademark-provider.ts` | `search` |
| `KeywordProvider` | `src/providers/keyword/keyword-provider.ts` | `getMetrics` |
| `CompsProvider` | `src/providers/comps/comps-provider.ts` | `getSales` |
| `WhoisProvider` | `src/providers/whois/whois-provider.ts` | `checkAvailability` |
| `RegistrarProvider` | `src/providers/registrar/registrar-provider.ts` | `checkPrice`, `purchase`, `listDomains`, `getRenewalCost` |
| `Notifier` | `src/notifiers/notifier.ts` | `send` |
### Registrar Integration
The `RegistrarProvider` interface allows automated domain purchases. Built-in
implementations:
- `ManualRegistrarProvider` — returns no-op; you buy domains manually (default)
- `CloudflareRegistrarProvider` — uses Cloudflare API v4 (set `CLOUDFLARE_API_TOKEN`
and `CLOUDFLARE_ACCOUNT_ID` in `.env`)
To add Namecheap, GoDaddy, or any other registrar:
1. Implement `RegistrarProvider`
2. Swap in `composition-root.ts`
---
## Trademark Matching
Trademark matching follows a conservative token-aware policy (see ADR-0012).
Three parameters control the matching behaviour:
| Env Variable | Default | Purpose |
| ------------------------------------------- | ------- | -------------------------------------------------------------------------------------- |
| `TRADEMARK_MIN_TOKEN_LENGTH_FUZZY` | `4` | Minimum token length for Levenshtein-1 matching |
| `TRADEMARK_MIN_MARK_TOKEN_LENGTH_SUBSTRING` | `3` | Minimum mark token length for substring matching |
| `TRADEMARK_MAX_LEVENSHTEIN` | `1` | Maximum edit distance for fuzzy matching |
| `TRADEMARK_PROVIDER_TIMEOUT_MS` | `15000` | Per-provider gate deadline (ms, min 1000); a fired deadline counts as provider failure |
### Strict USPTO TLDs
The trademark gate treats `.com` and `.us` domains as US-jurisdiction assets.
For these TLDs, the USPTO provider is mandatory — if USPTO is unreachable,
the gate returns `Unverified` even if EUIPO responded cleanly. This list is
hardcoded in `src/trademark/trademark-gate.ts` (`STRICT_USPTO_TLDS`).
To add more TLDs to this list, edit the `STRICT_USPTO_TLDS` set. This is one
of the few remaining hardcoded values, chosen deliberately for safety.
---
## Pipeline Behaviour
| Env Variable | Default | Purpose |
| --------------------------- | ------- | -------------------------------------------- |
| `DEFAULT_KEYWORD_TLD` | `.com` | TLD appended to bare keywords |
| `DNS_BULK_CONCURRENCY` | `200` | Concurrent DNS resolution requests |
| `WHOIS_LOOKUP_TIMEOUT` | `10000` | WHOIS socket timeout (ms) |
| `DROP_SCORE_THRESHOLD` | `25` | Score below which drop is considered (0-100) |
| `DROP_RENEWAL_HORIZON_DAYS` | `60` | Days before renewal for drop evaluation |
| `TM_CACHE_TTL_DAYS` | `7` | Trademark cache expiry in days |
---
## Notifications
DOMINUS supports multiple notification channels for renewal alerts and
watchlist events:
| Channel | Env Variables |
| -------- | ----------------------------------------------------------- |
| Console | Always active |
| Desktop | `NOTIFIER_DESKTOP_ENABLED=true` |
| Webhook | `NOTIFIER_WEBHOOK_URL` |
| Telegram | `NOTIFIER_TELEGRAM_BOT_TOKEN` + `NOTIFIER_TELEGRAM_CHAT_ID` |
Add a custom notifier by implementing the `Notifier` interface and adding it
in `src/notifiers/notifier-router.ts`.
---
## Deployment
See the [Deployment Guide](../deployment/README.md) for:
- Docker Compose profiles
- Reverse proxy configuration (nginx)
- systemd service unit
- PM2 ecosystem file
- Security checklist