ZhuLinsen/daily_stock_analysis · error · IntelligenceServiceError

intelligence source name already exists: {fields['name']}

Error message

intelligence source name already exists: {fields['name']}

What it means

IntelligenceService.create_source maps a DB IntegrityError (unique constraint on source name) into an IntelligenceServiceError. _normalize_source_fields strips and truncates the name to 100 chars before insert, so the collision is on the normalized name.

Source

Thrown at src/services/intelligence_service.py:153

    ):
        self.repo = repository or IntelligenceRepository()
        self.config = config or get_config()

    @classmethod
    def reset_auto_fetch_state(cls) -> None:
        with cls._auto_fetch_condition:
            cls._auto_fetch_in_progress = False
            cls._auto_fetch_last_run_at = None
            cls._auto_fetch_last_result = None
            cls._auto_fetch_condition.notify_all()

    def create_source(self, payload: Dict[str, Any]) -> Dict[str, Any]:
        fields = self._normalize_source_fields(payload)
        self._validate_url(fields["url"])
        try:
            return self._source_to_dict(self.repo.create_source(fields))
        except IntegrityError as exc:
            raise IntelligenceServiceError(f"intelligence source name already exists: {fields['name']}") from exc

    def list_sources(self, **filters: Any) -> Dict[str, Any]:
        rows, total = self.repo.list_sources(**filters)
        return {
            "items": [self._source_to_dict(row) for row in rows],
            "total": total,
            "page": max(1, int(filters.get("page") or 1)),
            "page_size": max(1, min(int(filters.get("page_size") or 50), 100)),
        }

    def list_source_templates(self, **filters: Any) -> Dict[str, Any]:
        market = str(filters.get("market") or "").strip().lower()
        source_type = str(filters.get("source_type") or "").strip().lower()
        templates = []
        for template in self._builtin_source_templates():
            if market and template["market"] != market:
                continue
            if source_type and template["source_type"] != source_type:

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. List existing sources first (list_sources) and reuse or pick a distinct name
  2. Make the create UI idempotent: disable submit while in flight and surface the duplicate error as a form-field message
  3. On collision, switch to update_source with the existing id instead

Example fix

# before
svc.create_source({'name': '财联社', 'url': '...'})  # duplicate -> error
# after
existing = next((s for s in svc.list_sources()['items'] if s['name']=='财联社'), None)
if existing:
    svc.update_source(existing['id'], {'url': '...'})
else:
    svc.create_source({'name': '财联社', 'url': '...'})
Defensive patterns

Strategy: try-catch

Validate before calling

existing = {s['name'] for s in svc.list_sources(page_size=100)['items']}
if payload['name'].strip() in existing:
    raise UserError('同名数据源已存在,请换一个名称')

Try / catch

try:
    svc.create_source(payload)
except IntelligenceServiceError as e:
    if 'already exists' in str(e):
        show_field_error('name', '该名称已被占用')
    else:
        raise

Prevention

When it happens

Trigger: POSTing a source whose name (after strip) equals an existing row's name; concurrent double-submit of the same create form; create_default_sources run after sources were already created.

Common situations: User double-clicks 'Create'; template-based creation colliding with previously created defaults; whitespace-only differences mistaken for unique names.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/0e964de0fcd836db. Report an issue: GitHub.