Skip to content

Commit 455b4cc

Browse files
committed
-updated other needed flags
-added data fetch for needed ids and meta from the sources -added test scripts
1 parent 724a625 commit 455b4cc

8 files changed

Lines changed: 309 additions & 3 deletions

File tree

test-001.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
# Categorize 500 Wikidata math items, fetch entity data, include external IDs
3+
cd "$(dirname "$0")/web" || exit 1
4+
5+
SESSION="test-001-$(date +%Y%m%d)"
6+
7+
python manage.py categorize \
8+
--limit 500 \
9+
--source Wd \
10+
--domain math \
11+
--fetch \
12+
--session-name "$SESSION"

test-002.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
# Categorize 500 Wikidata math items, no fetch, no external IDs
3+
cd "$(dirname "$0")/web" || exit 1
4+
5+
SESSION="test-002-$(date +%Y%m%d)"
6+
7+
python manage.py categorize \
8+
--limit 500 \
9+
--source Wd \
10+
--domain math \
11+
--session-name "$SESSION"

test-003.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
# Categorize 500 Wikidata physics items
3+
cd "$(dirname "$0")/web" || exit 1
4+
5+
SESSION="test-003-$(date +%Y%m%d)"
6+
7+
python manage.py categorize \
8+
--limit 500 \
9+
--source Wd \
10+
--domain phys \
11+
--session-name "$SESSION"

web/categorizer/categorizer_service.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
parse_categorization_result,
99
parse_categorization_result_with_reasoning,
1010
)
11+
from categorizer.wikidata_fetch_service import WikidataFetchService
1112
from concepts.models import CategorizerResult, Item
1213

1314
# Free LLM types to use for categorization
@@ -39,9 +40,16 @@ class CategorizerService:
3940
def __init__(self):
4041
self.logger = logging.getLogger(__name__)
4142
self.llm_service = LLMService()
43+
self.wikidata_fetch_service = WikidataFetchService()
4244

4345
def categorize_items(
44-
self, limit=None, judge_pool="low", domain=None, source=None, session_name=None
46+
self,
47+
limit=None,
48+
judge_pool="low",
49+
domain=None,
50+
source=None,
51+
fetch=False,
52+
session_name=None,
4553
):
4654
"""
4755
Categorize items from the database using all free LLM types.
@@ -90,14 +98,19 @@ def categorize_items(
9098
f"{len(pool)} LLMs)"
9199
)
92100

101+
use_other_ids = fetch
102+
93103
total_start = time.perf_counter()
94104
for i, item in enumerate(items_to_process):
95105
self.logger.info(f"Processing item {i + 1}/{to_process}: {item.identifier}")
106+
if fetch:
107+
self.wikidata_fetch_service.fetch_and_store_meta(item)
96108
self.categorize_item(
97109
item,
98110
pool=pool,
99111
session_name=session_name,
100112
judge_pool=judge_pool,
113+
use_other_ids=use_other_ids,
101114
)
102115

103116
total_elapsed = time.perf_counter() - total_start
@@ -112,6 +125,7 @@ def categorize_item(
112125
pool=None,
113126
session_name=None,
114127
judge_pool="low",
128+
use_other_ids=True,
115129
):
116130
"""
117131
Categorize a single item using all free LLM types.
@@ -123,6 +137,7 @@ def categorize_item(
123137
pool: List of LLMType to use (defaults to LLM_JUDGE_POOL)
124138
session_name: Optional session name to tag results
125139
judge_pool: Which pool tier ("low", "local", or "high")
140+
use_other_ids: Include external IDs from meta in prompt
126141
127142
Returns:
128143
List of categorization results from all LLMs
@@ -135,7 +150,7 @@ def categorize_item(
135150
self.logger.debug(f"Categorizing: {item.name}")
136151

137152
prompt = build_categorization_prompt(
138-
item, predicate, with_reasoning=use_reasoning
153+
item, predicate, with_reasoning=use_reasoning, use_other_ids=use_other_ids
139154
)
140155

141156
results = []

web/categorizer/management/commands/categorize.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ def add_arguments(self, parser):
3535
choices=Item.Source.values,
3636
help="Filter by source (e.g. Wd, nL, MW, PW, EoM, WpEN, AUm)",
3737
)
38+
parser.add_argument(
39+
"--fetch",
40+
action="store_true",
41+
default=False,
42+
help="Fetch entity data from the source API if missing in item.meta",
43+
)
3844
parser.add_argument(
3945
"--session-name",
4046
type=str,
@@ -47,6 +53,7 @@ def handle(self, *args, **options):
4753
judge_pool = options.get("judge_pool")
4854
domain = options.get("domain")
4955
source = options.get("source")
56+
fetch = options.get("fetch")
5057
session_name = options.get("session_name")
5158

5259
service = CategorizerService()
@@ -69,6 +76,7 @@ def handle(self, *args, **options):
6976
judge_pool=judge_pool,
7077
domain=domain,
7178
source=source,
79+
fetch=fetch,
7280
session_name=session_name,
7381
)
7482
self.stdout.write(self.style.SUCCESS("Categorization complete!"))

web/categorizer/prompts.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
SYSTEM_PROMPT_WITH_REASONING = """You are a categorization judge. Your task is to
1313
evaluate whether a given concept satisfies a specific predicate.
1414
15+
Be careful with concepts from adjacent domains such as physics, computer science,
16+
or engineering. A concept should only be classified as mathematical if it is
17+
primarily mathematical in nature. Concepts that merely use mathematics as a tool
18+
(e.g. quantum mechanics, signal processing) should not be considered mathematical
19+
concepts. When in doubt, consider whether the concept originates from or is
20+
primarily studied within mathematics.
21+
1522
You must respond with a structured answer containing:
1623
1. answer: yes or no
1724
2. confidence: a number from 0 to 100 (representing your confidence percentage)
@@ -24,14 +31,18 @@
2431
"""
2532

2633

27-
def build_categorization_prompt(item, predicate, with_reasoning=False):
34+
def build_categorization_prompt(
35+
item, predicate, with_reasoning=False, use_other_ids=True
36+
):
2837
"""
2938
Build a prompt for evaluating a concept against a predicate.
3039
3140
Args:
3241
item: Item instance to categorize
3342
predicate: The question/predicate to evaluate
3443
with_reasoning: If True, ask for reasoning in the response
44+
use_other_ids: If True, include external IDs from item.meta
45+
(only applies to Wikidata items)
3546
3647
Returns:
3748
Formatted prompt string
@@ -54,6 +65,11 @@ def build_categorization_prompt(item, predicate, with_reasoning=False):
5465
article_text = item.article_text[:1000]
5566
item_info_parts.append(f"Article text: {article_text}")
5667

68+
if use_other_ids:
69+
other_ids = _get_other_ids(item)
70+
if other_ids:
71+
item_info_parts.append(f"External IDs: {other_ids}")
72+
5773
item_info = "\n".join(item_info_parts)
5874

5975
prompt = f"""{system_prompt}
@@ -73,3 +89,33 @@ def build_categorization_prompt(item, predicate, with_reasoning=False):
7389
Please provide your evaluation in the format specified above."""
7490

7591
return prompt
92+
93+
94+
_OTHER_ID_KEYS = {
95+
"mathworld_id": "MathWorld ID",
96+
"nlab_id": "nLab ID",
97+
"proofwiki_id": "ProofWiki ID",
98+
"eom_id": "Encyclopedia of Mathematics ID",
99+
}
100+
101+
102+
def _get_other_ids(item):
103+
from concepts.models import Item
104+
105+
if item.source != Item.Source.WIKIDATA:
106+
return None
107+
if not item.meta:
108+
return None
109+
try:
110+
import json
111+
112+
meta = json.loads(item.meta)
113+
except (json.JSONDecodeError, TypeError):
114+
return None
115+
116+
parts = []
117+
for meta_key, label in _OTHER_ID_KEYS.items():
118+
value = meta.get(meta_key)
119+
if value:
120+
parts.append(f"{label}: {value}")
121+
return ", ".join(parts) if parts else None

web/categorizer/tests.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import json
2+
3+
from categorizer.wikidata_fetch_service import WikidataFetchService
4+
from django.test import TestCase
5+
6+
7+
class WikidataFetchServiceTest(TestCase):
8+
"""
9+
Live test against the Wikidata API.
10+
Run with: python manage.py test categorizer.tests.WikidataFetchServiceTest
11+
"""
12+
13+
def setUp(self):
14+
self.service = WikidataFetchService()
15+
16+
def test_fetch_entity_q2261345(self):
17+
"""Fetch Q2261345 and print the response for inspection."""
18+
entity_id = "Q2261345"
19+
entity = self.service.fetch_entity(entity_id)
20+
21+
self.assertIsNotNone(entity, "Expected entity data, got None")
22+
23+
# Basic structure
24+
self.assertIn("labels", entity)
25+
self.assertIn("descriptions", entity)
26+
self.assertIn("claims", entity)
27+
self.assertIn("sitelinks", entity)
28+
29+
# Print for inspection
30+
print(f"\n{'=' * 60}")
31+
print(f"Entity: {entity_id}")
32+
print(f"{'=' * 60}")
33+
34+
label = entity["labels"].get("en", {}).get("value")
35+
print(f"Label: {label}")
36+
37+
description = entity["descriptions"].get("en", {}).get("value")
38+
print(f"Description: {description}")
39+
40+
print(f"\nClaims ({len(entity['claims'])} properties):")
41+
for prop_id, claims in entity["claims"].items():
42+
values = []
43+
for claim in claims:
44+
mainsnak = claim.get("mainsnak", {})
45+
datavalue = mainsnak.get("datavalue", {})
46+
values.append(datavalue.get("value", "N/A"))
47+
print(f" {prop_id}: {values}")
48+
49+
print(f"\nSitelinks ({len(entity['sitelinks'])} wikis):")
50+
for site, link in entity["sitelinks"].items():
51+
print(f" {site}: {link['title']}")
52+
53+
print(f"\n{'=' * 60}")
54+
print(f"Full JSON:\n{json.dumps(entity, indent=2)[:3000]}")

0 commit comments

Comments
 (0)