# 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 { // 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 { // 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