Skip to content

Commit ab60bf9

Browse files
committed
feat: stop model-extracting the conference object entirely (0.9.4)
0.9.3 dropped conference date fields; this drops the rest (conferenceName, conferenceLocation, conferenceUri, conferenceAcronym) so poster2json never emits a conference object. Conference info comes from the repository or platform, not the poster. Prompt no longer requests it; the placeholder and grounding postprocess logic is removed and any conference the model emits is stripped.
1 parent 1a77b84 commit ab60bf9

4 files changed

Lines changed: 25 additions & 53 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.9.4] - 2026-06-08
9+
10+
### Changed
11+
12+
- **The `conference` object is no longer model-extracted.** 0.9.3 dropped the conference date fields; this release drops the rest (`conferenceName`, `conferenceLocation`, `conferenceUri`, `conferenceAcronym`) so poster2json never emits a `conference` object at all. Conference information is supplied by the repository or entered on the platform, not guessed from the poster. The prompt no longer requests it, the placeholder/grounding postprocess logic is removed, and any `conference` the model emits is stripped.
13+
814
## [0.9.3] - 2026-06-05
915

1016
### Changed

poster2json/extract.py

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,7 +1417,7 @@ def _generate(model, tokenizer, prompt: str, max_tokens: int) -> str:
14171417
EXTRACTION_PROMPT = """Convert this scientific poster text to JSON format.
14181418
14191419
CRITICAL RULES:
1420-
1. Extract ALL required fields: creators, titles, publicationYear, subjects, descriptions, conference
1420+
1. Extract ALL required fields: creators, titles, publicationYear, subjects, descriptions
14211421
2. Create SEPARATE sections for EACH distinct topic/header found in the poster
14221422
3. Use the poster's OWN section headers exactly as they appear. Lines prefixed with "## " indicate detected headers from the poster layout. Standard headers (Abstract, Introduction, Methods, Results, Discussion, Conclusions, References, Acknowledgements) are common examples, but always prefer the poster's actual headers over generic ones.
14231423
4. Each section must have its OWN "sectionTitle" and "sectionContent"
@@ -1435,7 +1435,6 @@ def _generate(model, tokenizer, prompt: str, max_tokens: int) -> str:
14351435
"publicationYear": null,
14361436
"subjects": [{{"subject": "keyword1"}}, {{"subject": "keyword2"}}, {{"subject": "keyword3"}}],
14371437
"descriptions": [{{"description": "A 3-4 sentence summary of the full poster...", "descriptionType": "Abstract"}}],
1438-
"conference": null,
14391438
"researchField": null,
14401439
"content": {{
14411440
"sections": [
@@ -1452,32 +1451,28 @@ def _generate(model, tokenizer, prompt: str, max_tokens: int) -> str:
14521451
- publicationYear: Extract if a year is printed on the poster. If not found, set to null.
14531452
- subjects: Extract 3-5 keywords from poster content
14541453
- descriptions: Write a 3-4 sentence summary of the full poster.
1455-
- titles: If the poster title is ALL CAPS, convert to proper Title Case preserving acronyms (e.g. "RESEARCH ON SARS-CoV-2" not "RESEARCH ON SARS-COV-2")
1456-
- conference: Extract from text visible on the poster (header, footer, logos). If not found, set to null.
1457-
- imageCaptions/tableCaptions: Include captions for figures/tables on the poster. If none exist, use [].
1454+
- titles: If the poster title is ALL CAPS, convert to proper Title Case preserving acronyms (e.g. "RESEARCH ON SARS-CoV-2" not "RESEARCH ON SARS-COV-2")- imageCaptions/tableCaptions: Include captions for figures/tables on the poster. If none exist, use [].
14581455
- researchField: MUST be exactly one of: "Health Sciences" | "Life Sciences" | "Physical Sciences" | "Social Sciences" — or null if unclear.
1459-
- GROUNDING: For metadata fields (conference, publicationYear), only extract values that appear as text on the poster. If not found, use null. For section content, copy ALL text verbatim — do not skip or shorten.
1456+
- GROUNDING: For metadata fields (publicationYear), only extract values that appear as text on the poster. If not found, use null. For section content, copy ALL text verbatim — do not skip or shorten.
14601457
14611458
POSTER TEXT TO CONVERT:
14621459
{raw_text}
14631460
14641461
OUTPUT VALID JSON ONLY:"""
14651462

14661463
FALLBACK_PROMPT = """Convert poster text to JSON. REQUIRED FIELDS:
1467-
1. creators, titles, publicationYear, subjects, descriptions, conference, content
1464+
1. creators, titles, publicationYear, subjects, descriptions, content
14681465
2. SEPARATE section for EACH header found in the poster text. Use the poster's own headers. Lines starting with "## " are detected headers.
14691466
3. Copy ALL text EXACTLY verbatim — every line of poster text must appear in a section
14701467
4. If title is ALL CAPS, convert to Title Case preserving acronyms (SARS-CoV-2, not SARS-COV-2)
1471-
5. conference: extract if visible on the poster. If not found, set to null.
1472-
6. imageCaptions/tableCaptions: for figures/tables on the poster. If none, use [].
1468+
5. imageCaptions/tableCaptions: for figures/tables on the poster. If none, use [].
14731469
14741470
{{
14751471
"creators": [{{"name": "LastName, FirstName", "givenName": "FirstName", "familyName": "LastName", "affiliation": ["Institution"]}}],
14761472
"titles": [{{"title": "Poster Title"}}],
14771473
"publicationYear": null,
14781474
"subjects": [{{"subject": "keyword1"}}, {{"subject": "keyword2"}}],
14791475
"descriptions": [{{"description": "3-4 sentence summary of the full poster", "descriptionType": "Abstract"}}],
1480-
"conference": null,
14811476
"researchField": null,
14821477
"content": {{
14831478
"sections": [{{"sectionTitle": "Header", "sectionContent": "Full verbatim text of this section..."}}]
@@ -2026,39 +2021,9 @@ def _postprocess_json(
20262021
elif isinstance(result[key], (dict, list)):
20272022
result[key] = _normalize_captions(result[key], caption_type=ctype)
20282023

2029-
# Strip placeholder/hallucinated conference values
2030-
conf = result.get("conference")
2031-
if isinstance(conf, dict):
2032-
# Conference dates are not extracted by the model; they come from the
2033-
# repository or are entered on the platform.
2034-
for _k in ("conferenceStartDate", "conferenceEndDate", "conferenceYear"):
2035-
conf.pop(_k, None)
2036-
cn = conf.get("conferenceName", "")
2037-
if isinstance(cn, str) and _is_placeholder(cn):
2038-
conf.pop("conferenceName", None)
2039-
# Ground-truth check: conference name must appear in the poster text
2040-
cn = conf.get("conferenceName", "")
2041-
if isinstance(cn, str) and cn and raw_text:
2042-
cn_lower = cn.lower().strip()
2043-
if cn_lower not in raw_text.lower():
2044-
result["conference"] = None
2045-
conf = None
2046-
if conf is not None:
2047-
cl = conf.get("conferenceLocation", "")
2048-
if isinstance(cl, str) and _is_placeholder(cl):
2049-
conf.pop("conferenceLocation", None)
2050-
cu = conf.get("conferenceUrl", "")
2051-
if isinstance(cu, str) and _is_placeholder(cu):
2052-
conf.pop("conferenceUrl", None)
2053-
# Fix 4: validate conferenceUri is a real URL
2054-
for uri_key in ("conferenceUri", "conferenceUrl"):
2055-
val = conf.get(uri_key, "")
2056-
if isinstance(val, str) and val and not val.startswith(("http://", "https://")):
2057-
conf.pop(uri_key, None)
2058-
# Collapse to null if no meaningful fields remain
2059-
meaningful = {k for k, v in conf.items() if v}
2060-
if not meaningful:
2061-
result["conference"] = None
2024+
# Conference is not extracted by the model at all; it is supplied by the
2025+
# repository or entered on the platform. Drop anything the model emits.
2026+
result.pop("conference", None)
20622027

20632028
# Publisher is auto-set by posters.science — strip if the LLM emitted one
20642029
result.pop("publisher", None)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22

33
name = "poster2json"
4-
version = "0.9.3"
4+
version = "0.9.4"
55
description = "Convert scientific posters (PDF/images) to structured JSON metadata using Large Language Models"
66

77
packages = [{ include = "poster2json" }]

tests/test_normalize.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,17 +193,16 @@ def test_conference_placeholder_stripped():
193193
}
194194
}
195195
out = _postprocess_json(data, raw_text="")
196-
# conferenceName is a placeholder and conferenceYear is dropped (dates are
197-
# not model-extracted), so the object collapses to null.
198-
assert out["conference"] is None
196+
# conference is no longer model-extracted at all; it is dropped entirely.
197+
assert "conference" not in out
199198

200199

201200
def test_conference_all_placeholder_collapses_to_null():
202201
from poster2json.extract import _postprocess_json
203202

204203
data = {"conference": {"conferenceName": "Name of Conference", "conferenceLocation": "City, Country"}}
205204
out = _postprocess_json(data, raw_text="")
206-
assert out["conference"] is None
205+
assert "conference" not in out
207206

208207

209208
def test_bogus_table_caption_filtered():
@@ -230,25 +229,27 @@ def test_publisher_stripped_from_output():
230229
assert "publisher" not in out
231230

232231

233-
def test_real_conference_preserved():
232+
def test_conference_dropped_even_when_real():
234233
from poster2json.extract import _postprocess_json
235234

236235
data = {"conference": {"conferenceName": "US-RSE'25", "conferenceYear": 2025}}
237236
out = _postprocess_json(data, raw_text="")
238-
assert out["conference"]["conferenceName"] == "US-RSE'25"
237+
# Even a real, well-formed conference is dropped — it comes from metadata.
238+
assert "conference" not in out
239239

240240

241-
def test_conference_grounded_in_poster_text():
241+
def test_conference_dropped_even_when_grounded():
242242
from poster2json.extract import _postprocess_json
243243

244244
data = {"conference": {"conferenceName": "ICML 2025", "conferenceYear": 2025}}
245245
out = _postprocess_json(data, raw_text="Presented at ICML 2025, Vancouver")
246-
assert out["conference"]["conferenceName"] == "ICML 2025"
246+
# No grounding logic remains; conference is dropped regardless of the text.
247+
assert "conference" not in out
247248

248249

249250
def test_conference_not_in_poster_text_stripped():
250251
from poster2json.extract import _postprocess_json
251252

252253
data = {"conference": {"conferenceName": "US-RSE'25", "conferenceYear": 2025}}
253254
out = _postprocess_json(data, raw_text="Drug-polymer interactions in nanoparticle delivery systems")
254-
assert out["conference"] is None
255+
assert "conference" not in out

0 commit comments

Comments
 (0)