"""Pinned T78 evidence, edition-local aliases and an explicitly identified material."""
import copy
import hashlib
import json
from pathlib import Path
import sqlite3
import tempfile
import unittest
import xml.etree.ElementTree as ET
from paper_atlas.collectors.acl import collect_xml, volume_scope
from paper_atlas.collectors.acl_historical import event_scope, material_reason
from paper_atlas.db.store import Store
from paper_atlas.ingestion.records import ingest_file
FIXTURE = Path(__file__).parent/'fixtures/acl-t78.xml'
COMMIT = 'a7be623d01c48c61405dec31c871121cdbed5a01'
class AclHistoricalTests(unittest.TestCase):
def setUp(self):
self.root = ET.parse(FIXTURE).getroot()
self.volume = self.root.find('volume')
self.meta = self.volume.find('meta')
def test_frozen_volume_matches_official_snapshot(self):
self.assertEqual(hashlib.sha256(FIXTURE.read_bytes()).hexdigest(), 'f712f94cff0cba05f786b7ac31dad2ea9408fde2a17a04531130df623f57dd84')
self.assertEqual(len(self.volume.findall('paper')), 36)
self.assertEqual(volume_scope(self.meta, '1', 'T78'), ('ACL+TINLAP', ['ACL', 'TINLAP'], 'proceedings_unspecified'))
def test_unknown_year_collection_or_volume_is_not_globally_aliased(self):
for collection, volume in [('T75', '1'), ('T87', '1'), ('T78', '2'), ('C65', '1')]:
with self.subTest(collection=collection, volume=volume):
self.assertIsNone(event_scope(self.meta, volume, collection))
self.assertIsNone(volume_scope(self.meta, volume, collection))
self.assertIsNone(volume_scope(self.meta, '1'))
def test_known_volume_scope_rejects_critical_metadata_drift(self):
for field, replacement in [('venue', 'acl'), ('year', '1979'), ('booktitle', 'Another TINLAP'), ('editor/first', 'Another')]:
changed = copy.deepcopy(self.meta)
changed.find(field).text = replacement
with self.subTest(field=field), self.assertRaises(ValueError):
event_scope(changed, '1', 'T78')
changed = copy.deepcopy(self.meta)
ET.SubElement(changed, 'year').text = '1978'
with self.assertRaises(ValueError):
event_scope(changed, '1', 'T78')
def test_evidence_retains_original_series_dates_and_role_boundary(self):
scope = event_scope(self.meta, '1', 'T78')
self.assertEqual(scope['original_venue'], 'TINLAP')
self.assertEqual(scope['event_dates'], ['1978-07-25', '1978-07-27'])
self.assertEqual([e['pdf_pages'] for e in scope['evidence']], [[9, 10], [3]])
self.assertIn('not identified', scope['track_scope_note'])
self.assertIn('pending', scope['evidence'][0]['scope'])
def test_only_exact_session_questions_material_is_excluded(self):
paper = copy.deepcopy(self.volume.findall('paper')[-1])
self.assertIn('Session question list', material_reason('T78', '1', paper))
self.assertIsNone(material_reason('C65', '1', paper))
self.assertIsNone(material_reason('T78', '2', paper))
paper.set('id', '37')
self.assertIsNone(material_reason('T78', '1', paper))
paper.set('id', '36')
ET.SubElement(paper, 'author')
with self.assertRaises(ValueError):
material_reason('T78', '1', paper)
def test_all_authored_source_fields_and_material_receipt_survive_collection(self):
with tempfile.TemporaryDirectory() as temporary:
manifest, = collect_xml(FIXTURE, temporary, COMMIT)
rows = [json.loads(raw) for raw in Path(manifest['file']).read_text().splitlines()]
self.assertEqual(manifest['count'], 35)
self.assertEqual(manifest['xml_paper_elements'], 36)
self.assertEqual(manifest['missing_abstract_count'], 35)
self.assertEqual(manifest['excluded_materials'][0]['external_id'], 'T78-1036')
for number, row in enumerate(rows, 1):
paper = self.volume.findall('paper')[number-1]
self.assertEqual(row['external_id'], f'T78-1{number:03d}')
self.assertEqual(row['title'], ' '.join(''.join(paper.find('title').itertext()).split()))
self.assertEqual(row['metadata']['venue_codes'], ['tinlap'])
self.assertEqual(row['metadata']['historical_event_scope']['original_venue'], 'TINLAP')
self.assertEqual(row['metadata']['metadata_commit'], COMMIT)
self.assertEqual((row['status'], row['track'], row['abstract']), ('accepted', 'proceedings_unspecified', None))
def test_old_coling_scope_is_unchanged_by_calendar_replacement_evidence(self):
meta = ET.fromstring('coling1965COLING 1965')
self.assertEqual(volume_scope(meta, '1', 'C65'), ('COLING', ['COLING'], 'main'))
self.assertIsNone(event_scope(meta, '1', 'C65'))
def test_native_ingestion_does_not_alias_other_tinlap_years_or_infer_negative_status(self):
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
manifest, = collect_xml(FIXTURE, root/'source', COMMIT)
db = root/'sample.sqlite'
store = Store(db)
pid, rid = store.record('test_control', 'tinlap1975', 'https://example.org/tinlap1975',
'An earlier TINLAP contribution', None, ['Jane Doe'])
store.decision(pid, rid, 'TINLAP', 1975, 'main', 'tinlap1975', 'https://example.org/tinlap1975', 'accepted', 'test control')
store.conn.commit()
store.conn.close()
ingest_file(db, manifest['file'])
ingest_file(db, manifest['file'])
with sqlite3.connect(db) as c:
self.assertEqual(c.execute('SELECT count(*) FROM papers').fetchone()[0], 36)
self.assertEqual(c.execute('SELECT count(*) FROM submissions').fetchone()[0], 36)
self.assertEqual(c.execute('SELECT count(*) FROM decisions').fetchone()[0], 36)
self.assertEqual(c.execute("SELECT count(*) FROM decisions WHERE status!='accepted'").fetchone()[0], 0)
self.assertIsNone(c.execute("SELECT 1 FROM source_records WHERE external_id='T78-1036'").fetchone())
aliases = c.execute('SELECT v.acronym,a.alias FROM venue_aliases a JOIN venues v ON v.id=a.venue_id ORDER BY a.alias').fetchall()
self.assertEqual(aliases, [('ACL+TINLAP', 'ACL'), ('ACL+TINLAP', 'TINLAP')])
metadata = json.loads(c.execute("SELECT metadata_json FROM source_records WHERE external_id='T78-1001'").fetchone()[0])
self.assertEqual(metadata['historical_event_scope']['event_year'], 1978)
self.assertEqual(c.execute('PRAGMA foreign_key_check').fetchall(), [])