osintengine 1.0.2__tar.gz → 1.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {osintengine-1.0.2 → osintengine-1.0.4}/PKG-INFO +4 -4
- {osintengine-1.0.2 → osintengine-1.0.4}/README.md +3 -3
- {osintengine-1.0.2 → osintengine-1.0.4}/pyproject.toml +1 -1
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/__init__.py +1 -1
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/engine.py +47 -11
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/synthesis.py +28 -20
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/pipeline/pre_synthesis.py +1 -1
- {osintengine-1.0.2 → osintengine-1.0.4}/.env.example +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/LICENSE +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/MANIFEST.in +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/SELF_HOSTING.md +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/WATSON_ARCHITECTURE.md +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/deploy.sh +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/osintengine.egg-info/SOURCES.txt +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/requirements.txt +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/setup.cfg +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/agent/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/agents/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/agents/protocol.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/auth/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/auth/store.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/conversation.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/core/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/core/models.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/ethics.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/exports.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/graph/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/graph/entities.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/graph/graph.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/graph/osint_framework.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/graph/relationships.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/graph/transforms.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/infra/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/infra/cache.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/infra/ratelimit.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/infra/resilience.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/infra/retry.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/metrics.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/opsec/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/executor.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/intent.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/intent_classifier.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/llm_config.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/resolution.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/scraper.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/orchestration/target_profile.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/persistence/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/persistence/models.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/persistence/store.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/pipeline/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/search.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/serializers/stix.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/base.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/blockchain.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/captcha.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/conflict.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/corporate.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/darkweb.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/geolocation.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/image_video.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/marinetraffic.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/people.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/registry.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/satellite.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/scraper.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/shodan.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/social_media.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/websites.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/tools/wikidata.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/utils/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/utils/helpers.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/utils/http.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/src/watson/verification/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/tests/conftest.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/tests/test_infra.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/tests/test_integration.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/tests/test_persistence.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/tests/test_resolution.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/tests/test_synthesis.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/agents/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/agents/base.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/agents/direct.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/agents/hermes.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/agents/hermes_mcp.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/api_keys.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/browser_scraper.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/cli.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/crossref.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/ethics.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/graph.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/mcp_server.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/memory.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/neo4j_graph.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/opsec/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/reporter.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/scheduler.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/serializers/stix.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/terminal.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/toolkit.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/toolkit_api.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/toolkit_automation.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/toolkit_registry.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/verification/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/__init__.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/app.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/middleware.py +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/static/assets/index-B7hPOc0z.js +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/static/assets/index-CJ6FP8Mp.css +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/static/assets/watson-logo-Dk9tawHb.png +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/static/index.html +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/templates/chat.html +0 -0
- {osintengine-1.0.2 → osintengine-1.0.4}/watson/web/templates/investigation-map.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: osintengine
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: The OSINT investigation engine — 7-phase pipeline, persistent knowledge graph, community intelligence
|
|
5
5
|
Author-email: Lorenzo Baron <baron.lorenzo99@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -65,9 +65,9 @@ Dynamic: license-file
|
|
|
65
65
|
</p>
|
|
66
66
|
|
|
67
67
|
<p align="center">
|
|
68
|
-
<a href="https://pypi.org/project/osintengine/"><img src="https://img.shields.io/badge/pypi-v1.0.
|
|
68
|
+
<a href="https://pypi.org/project/osintengine/"><img src="https://img.shields.io/badge/pypi-v1.0.3-blue" alt="PyPI"></a>
|
|
69
69
|
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue" alt="Python"></a>
|
|
70
|
-
<a href="LICENSE"><img src="https://img.shields.io/badge/license-
|
|
70
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-green" alt="License"></a>
|
|
71
71
|
<a href="https://github.com/Lorenzobaron99/watson-osint"><img src="https://img.shields.io/badge/status-active-brightgreen" alt="Status"></a>
|
|
72
72
|
</p>
|
|
73
73
|
|
|
@@ -85,7 +85,7 @@ One target. One command. A dossier that would take an analyst hours.
|
|
|
85
85
|
|
|
86
86
|
| You type... | Most tools return... | Watson returns... |
|
|
87
87
|
|---|---|---|
|
|
88
|
-
| "
|
|
88
|
+
| "John Chen" | Wikipedia link, maybe a news article | Employment history, board positions, corporate registrations, domain ownerships, sanctions screening, adverse media, timeline of professional milestones, entity relationship graph |
|
|
89
89
|
| "example.com" | WHOIS record | Domain history, SSL certificates, subdomains, DNS records, IP geolocation, hosting provider, Wayback snapshots, linked social accounts, VirusTotal reputation |
|
|
90
90
|
| "crypto-wallet-0x..." | Nothing useful | Transaction graph, exchange links, associated addresses, risk scoring, dark web mentions |
|
|
91
91
|
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<a href="https://pypi.org/project/osintengine/"><img src="https://img.shields.io/badge/pypi-v1.0.
|
|
12
|
+
<a href="https://pypi.org/project/osintengine/"><img src="https://img.shields.io/badge/pypi-v1.0.3-blue" alt="PyPI"></a>
|
|
13
13
|
<a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%2B-blue" alt="Python"></a>
|
|
14
|
-
<a href="LICENSE"><img src="https://img.shields.io/badge/license-
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-green" alt="License"></a>
|
|
15
15
|
<a href="https://github.com/Lorenzobaron99/watson-osint"><img src="https://img.shields.io/badge/status-active-brightgreen" alt="Status"></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
@@ -29,7 +29,7 @@ One target. One command. A dossier that would take an analyst hours.
|
|
|
29
29
|
|
|
30
30
|
| You type... | Most tools return... | Watson returns... |
|
|
31
31
|
|---|---|---|
|
|
32
|
-
| "
|
|
32
|
+
| "John Chen" | Wikipedia link, maybe a news article | Employment history, board positions, corporate registrations, domain ownerships, sanctions screening, adverse media, timeline of professional milestones, entity relationship graph |
|
|
33
33
|
| "example.com" | WHOIS record | Domain history, SSL certificates, subdomains, DNS records, IP geolocation, hosting provider, Wayback snapshots, linked social accounts, VirusTotal reputation |
|
|
34
34
|
| "crypto-wallet-0x..." | Nothing useful | Transaction graph, exchange links, associated addresses, risk scoring, dark web mentions |
|
|
35
35
|
|
|
@@ -1927,7 +1927,7 @@ Examples:
|
|
|
1927
1927
|
# FORCE a targeted person search excluding the non-person Wikipedia hit
|
|
1928
1928
|
# Universal fix: when Wikipedia returns a location/org/thing instead of a person,
|
|
1929
1929
|
# search with -site:wikipedia.org + person keywords. Works for:
|
|
1930
|
-
# -
|
|
1930
|
+
# - Marco Rossi (comune) → "Marco Rossi" analyst defense
|
|
1931
1931
|
# - Austin Texas (city) → "Austin Texas" linkedin
|
|
1932
1932
|
# - Paris Hilton (city) → "Paris Hilton" celebrity (though common enough to find anyway)
|
|
1933
1933
|
try:
|
|
@@ -2808,7 +2808,7 @@ Examples:
|
|
|
2808
2808
|
# employment, board positions) — essential context even for criminal
|
|
2809
2809
|
# investigations. Criminal queries add sanctions, court records, and
|
|
2810
2810
|
# wanted notices. Without professional queries, deep investigations
|
|
2811
|
-
# miss entire professional profiles (e.g. "
|
|
2811
|
+
# miss entire professional profiles (e.g. "Marco Rossi" → only found
|
|
2812
2812
|
# jail lookup sites, never discovered eurodefense.tech).
|
|
2813
2813
|
search_label = "searching professional history + court records, legal databases"
|
|
2814
2814
|
search_mode = "both"
|
|
@@ -3616,7 +3616,9 @@ Examples:
|
|
|
3616
3616
|
except Exception as e:
|
|
3617
3617
|
logger.warning("resolution_failed: %s", e)
|
|
3618
3618
|
|
|
3619
|
-
# Synthesis
|
|
3619
|
+
# Synthesis — filter noise before feeding to LLM
|
|
3620
|
+
findings = self._filter_quality(findings, query=query)
|
|
3621
|
+
|
|
3620
3622
|
try:
|
|
3621
3623
|
from .synthesis import synthesize_brief
|
|
3622
3624
|
from .llm_config import call_llm
|
|
@@ -3802,14 +3804,14 @@ Examples:
|
|
|
3802
3804
|
primary = sum(1 for f in findings if f.source_tier == "PRIMARY")
|
|
3803
3805
|
has_url = sum(1 for f in findings if f.source_url)
|
|
3804
3806
|
|
|
3805
|
-
# Verifiability score — weighted:
|
|
3806
|
-
#
|
|
3807
|
-
#
|
|
3807
|
+
# Verifiability score — weighted: confirmed findings + primary sources dominate.
|
|
3808
|
+
# CONFIRMED findings are the gold standard; PRIMARY sources (court docs, sanctions)
|
|
3809
|
+
# carry the most weight. PROBABLE adds marginal confidence.
|
|
3808
3810
|
report.verifiability_score = (
|
|
3809
|
-
0.
|
|
3811
|
+
0.35 * (confirmed / max(total, 1)) +
|
|
3812
|
+
0.30 * (primary / max(total, 1)) +
|
|
3810
3813
|
0.25 * (has_url / max(total, 1)) +
|
|
3811
|
-
0.
|
|
3812
|
-
0.35 * (probable / max(total, 1))
|
|
3814
|
+
0.10 * (probable / max(total, 1))
|
|
3813
3815
|
)
|
|
3814
3816
|
|
|
3815
3817
|
lines = [
|
|
@@ -4830,7 +4832,7 @@ and confidence assessments. Follow the OUTPUT FORMAT specified above."""
|
|
|
4830
4832
|
"""Compute token-overlap relevance between target query and finding text.
|
|
4831
4833
|
|
|
4832
4834
|
Returns 0.0 – 1.0. A score of 0 means zero word overlap — the finding
|
|
4833
|
-
is almost certainly unrelated (e.g., "Idaho murders" for "
|
|
4835
|
+
is almost certainly unrelated (e.g., "Idaho murders" for "KOVÁCS Anna").
|
|
4834
4836
|
"""
|
|
4835
4837
|
import unicodedata
|
|
4836
4838
|
if not target or not text:
|
|
@@ -5004,7 +5006,7 @@ and confidence assessments. Follow the OUTPUT FORMAT specified above."""
|
|
|
5004
5006
|
"no family members identified",
|
|
5005
5007
|
"no specific co-conspirator names",
|
|
5006
5008
|
"no specific fine", "no specific asset",
|
|
5007
|
-
# ── Leaked through
|
|
5009
|
+
# ── Leaked through KOVÁCS investigation ──
|
|
5008
5010
|
"no specific legal citations",
|
|
5009
5011
|
"no court identifiers",
|
|
5010
5012
|
"no child details",
|
|
@@ -5111,6 +5113,24 @@ and confidence assessments. Follow the OUTPUT FORMAT specified above."""
|
|
|
5111
5113
|
"i will run", "i will simulate", "i must ask",
|
|
5112
5114
|
"you have not", "if you can", "instruct me",
|
|
5113
5115
|
"once you provide", "example of what",
|
|
5116
|
+
# ── Leaked through Mohammadzadeh investigation ──
|
|
5117
|
+
"let's first extract", "let's first",
|
|
5118
|
+
"i will open", "i'll open", "i can use python",
|
|
5119
|
+
"i'm not actually able", "i recall the",
|
|
5120
|
+
"for each fact", "i'll also", "i'll keep",
|
|
5121
|
+
"i'll treat", "i'll use", "i can use",
|
|
5122
|
+
"the youtube video is likely",
|
|
5123
|
+
"however, the user also",
|
|
5124
|
+
"i will incorporate", "i can incorporate",
|
|
5125
|
+
"i could open", "i could use",
|
|
5126
|
+
"i don't have the full", "i can view",
|
|
5127
|
+
"i could mention", "i'll rely on",
|
|
5128
|
+
"but i can't see", "i can't see the full",
|
|
5129
|
+
"the search result snippet says",
|
|
5130
|
+
"for iran:", "i can mention that",
|
|
5131
|
+
"as an ai in this simulation",
|
|
5132
|
+
"i need to fetch", "i might need",
|
|
5133
|
+
"i will treat", "i can mention",
|
|
5114
5134
|
])
|
|
5115
5135
|
|
|
5116
5136
|
if is_meta and not (has_proper_names or has_dates or has_source_ref):
|
|
@@ -5121,6 +5141,22 @@ and confidence assessments. Follow the OUTPUT FORMAT specified above."""
|
|
|
5121
5141
|
f.source_tier = "UNVERIFIED"
|
|
5122
5142
|
f.confidence = min(f.confidence, 0.25)
|
|
5123
5143
|
|
|
5144
|
+
# ── Drop: DNS/IP resolution noise when target is a person ──
|
|
5145
|
+
# Graph enrichment resolves every domain in findings to IPs and geolocates them.
|
|
5146
|
+
# For person investigations, this produces 14+ noise findings (Cloudflare → Toronto).
|
|
5147
|
+
# Only keep DNS/IP enrichment when the investigation target IS a domain or IP.
|
|
5148
|
+
if getattr(f, 'phase', '') == 'graph_enrich':
|
|
5149
|
+
tf = (f.title or '').lower()
|
|
5150
|
+
df = (f.description or '').lower()
|
|
5151
|
+
# DNS resolution or IP geolocation that's about infrastructure, not the target
|
|
5152
|
+
dns_patterns = [
|
|
5153
|
+
'dns a record', 'resolves_to', '→ resolves_to',
|
|
5154
|
+
'→ located_in', 'located_in', 'ip geolocation',
|
|
5155
|
+
'cloudflare, inc.', '→ toronto', '→ san francisco',
|
|
5156
|
+
]
|
|
5157
|
+
if any(p in tf or p in df for p in dns_patterns):
|
|
5158
|
+
continue
|
|
5159
|
+
|
|
5124
5160
|
kept.append(f)
|
|
5125
5161
|
|
|
5126
5162
|
# ── Relevance check: universal pre-synthesis pipeline (any target type, any language) ──
|
|
@@ -560,37 +560,45 @@ def _fallback_brief(query: str, findings: list) -> dict:
|
|
|
560
560
|
"Financial": ["revenue", "profit", "loss", "billion", "million", "funding", "valuation",
|
|
561
561
|
"stock", "share", "investor", "ipo", "acquisition"],
|
|
562
562
|
"Crime / Investigation": ["criminal", "arrest", "prison", "jail", "fraud", "money laundering",
|
|
563
|
-
"investigation", "probe", "allegation", "misconduct"
|
|
564
|
-
|
|
565
|
-
"subdomain", "resolves_to", "mx record"],
|
|
563
|
+
"investigation", "probe", "allegation", "misconduct", "wanted", "fugitive",
|
|
564
|
+
"fbi", "hacker", "cyber"],
|
|
566
565
|
}
|
|
567
566
|
themes = []
|
|
568
567
|
all_text = " ".join(titles + [getattr(f, "description", "") or "" for f in findings]).lower()
|
|
569
568
|
for theme, keywords in theme_keywords.items():
|
|
570
569
|
matches = [kw for kw in keywords if kw in all_text]
|
|
571
570
|
if matches:
|
|
572
|
-
#
|
|
571
|
+
# Find the best finding for this theme — prefer ones with real descriptions
|
|
572
|
+
best_summary = f"Evidence of {matches[0]} found across multiple sources."
|
|
573
|
+
best_source = ""
|
|
573
574
|
for f in findings:
|
|
574
|
-
desc = (getattr(f, "description", "") or "")
|
|
575
|
+
desc = (getattr(f, "description", "") or "")
|
|
575
576
|
title_l = (getattr(f, "title", "") or "").lower()
|
|
576
577
|
for kw in matches:
|
|
577
|
-
if kw in title_l or kw in desc:
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
"source_titles": [getattr(f, "title", "")[:100]],
|
|
583
|
-
})
|
|
578
|
+
if kw in title_l or kw in desc.lower():
|
|
579
|
+
# Use the description as summary (actual intelligence, not just title)
|
|
580
|
+
if desc and len(desc) > 20:
|
|
581
|
+
best_summary = desc[:300]
|
|
582
|
+
best_source = getattr(f, "source_url", "") or ""
|
|
584
583
|
break
|
|
585
|
-
if
|
|
584
|
+
if best_source:
|
|
586
585
|
break
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
586
|
+
themes.append({
|
|
587
|
+
"theme": theme,
|
|
588
|
+
"severity": "HIGH" if theme.startswith("Crime") else "MEDIUM",
|
|
589
|
+
"summary": best_summary,
|
|
590
|
+
"source_titles": [best_source] if best_source else [],
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
# Deduplicate themes — same summary shouldn't appear for multiple themes
|
|
594
|
+
seen_summaries = set()
|
|
595
|
+
deduped_themes = []
|
|
596
|
+
for t in themes:
|
|
597
|
+
key = t["summary"][:50]
|
|
598
|
+
if key not in seen_summaries:
|
|
599
|
+
seen_summaries.add(key)
|
|
600
|
+
deduped_themes.append(t)
|
|
601
|
+
themes = deduped_themes[:5]
|
|
594
602
|
|
|
595
603
|
# Extract timeline events from findings (dates in titles/descriptions)
|
|
596
604
|
timeline_events: list[dict] = []
|
|
@@ -501,7 +501,7 @@ def relevance_score(profile: TargetProfile, text: str) -> float:
|
|
|
501
501
|
|
|
502
502
|
# ── Strategy 2.5: Surname-aware penalty (person targets only) ──
|
|
503
503
|
# For multi-word person names, a finding that matches only ONE name part
|
|
504
|
-
# (e.g., "
|
|
504
|
+
# (e.g., "Anna Kovacs" matching only "Anna" from "KOVÁCS Anna") is a
|
|
505
505
|
# completely different person. Given names are too common; the surname is
|
|
506
506
|
# the primary disambiguator. This penalty prevents the trigram/Jaccard
|
|
507
507
|
# overlap from giving high scores to clearly irrelevant findings.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|