Compare commits

...

1 Commits

Author SHA1 Message Date
codex-bot b4e249e675 Recognize accepted RSS publisher DOI as an edition-scoped publication identity
Metadata integrity checks / metadata (push) Successful in 2m55s
2026-11-15 23:07:11 +08:00
3 changed files with 66 additions and 1 deletions
+6
View File
@@ -342,3 +342,9 @@ ICML 2024 起的一个 PMLR 卷可以同时包含研究主轨道与 Position Pap
2024 年的官方节目单把两轨都放在 `Conference`,但提供 PMLR 论文自链接。该年仅采用组织方在 [2025 Position CFP](https://icml.cc/Conferences/2025/CallForPositionPapers) 明确链接的 [2024 Position 论文标题筛选](https://icml.cc/virtual/2024/papers.html?filter=titles&search=position%3A) 规则;同时保存规则页面的哈希。这个规则不用于其他会议或年份。官方节目单将 `guyard24a` 链接误写为 `theo24a`:只对这条已核查的映射额外要求标题精确一致、至少两名公开作者重合,不进行全库模糊匹配。 2024 年的官方节目单把两轨都放在 `Conference`,但提供 PMLR 论文自链接。该年仅采用组织方在 [2025 Position CFP](https://icml.cc/Conferences/2025/CallForPositionPapers) 明确链接的 [2024 Position 论文标题筛选](https://icml.cc/virtual/2024/papers.html?filter=titles&search=position%3A) 规则;同时保存规则页面的哈希。这个规则不用于其他会议或年份。官方节目单将 `guyard24a` 链接误写为 `theo24a`:只对这条已核查的映射额外要求标题精确一致、至少两名公开作者重合,不进行全库模糊匹配。
导入与身份回填拒绝 ICML 2024 起 parser_version<3 或没有官方轨道证据的旧文件。已有主库中的错误轨道需要在独占维护锁内迁移原投稿事件、保留决定与来源关联;仅重新导入拆分文件会留下旧的主轨道事件,不能作为修复方式。完成迁移前,不把新文件直接导入现有库。 导入与身份回填拒绝 ICML 2024 起 parser_version<3 或没有官方轨道证据的旧文件。已有主库中的错误轨道需要在独占维护锁内迁移原投稿事件、保留决定与来源关联;仅重新导入拆分文件会留下旧的主轨道事件,不能作为修复方式。完成迁移前,不把新文件直接导入现有库。
### RSS 发表身份
已核验的 `rss_proceedings` 录用记录与其他出版社来源一样,将 DOI 作为届次范围内的 `publication_doi` 投稿身份。这样同一 RSS 出版物先由官方论文集、后由 DBLP 导入(或反向)时可以共享一次投稿,并保留各自来源和决定观察。此规则不把节目显示编号当原始 OpenReview 投稿号,不把拒稿/撤稿升级为录用,也不跨届次合并历史。
这不证明任意 DOI 的科学作品归属:导入前仍需核查原始标题、完整作者、版本和已有标识冲突。变更只作用于后续导入;不会自动回填、合并或改写旧 RSS 记录,旧记录需要独立审查计划。
+1 -1
View File
@@ -4,7 +4,7 @@ import re
from paper_atlas.io.publication_ids import mlsys_id from paper_atlas.io.publication_ids import mlsys_id
PUBLICATIONS={'pmlr','acl_anthology','colm','neurips','mlsys','cvf','ecva','dblp','aamas_proceedings'} PUBLICATIONS={'pmlr','acl_anthology','colm','neurips','mlsys','cvf','ecva','dblp','aamas_proceedings','rss_proceedings'}
def timestamp(value=None): def timestamp(value=None):
+59
View File
@@ -0,0 +1,59 @@
"""RSS publisher/DBLP observations share one event without losing old decisions."""
from pathlib import Path
import json
import sqlite3
import tempfile
import unittest
from paper_atlas.db.submissions import identities_for_row
from tests.test_aamas_publication_identity import row, ingest
DOI='10.15607/rss.2026.xxii.041'
def rss(source,external,**kwargs):
return row(source,external,venue='RSS',year=2026,track='main',
identifiers=kwargs.pop('identifiers',{'doi':DOI}),**kwargs)
class RssPublicationIdentityTests(unittest.TestCase):
def test_only_verified_accepted_source_adds_publication_identity(self):
r=rss('rss_proceedings','2026/p041',identifiers={'doi':'https://doi.org/'+DOI.upper()})
self.assertEqual(identities_for_row(r),[('publication_doi',DOI)])
for status in ('rejected','desk_rejected','withdrawn','under_review','unknown'):
self.assertEqual(identities_for_row({**r,'status':status}),[])
self.assertEqual(identities_for_row({**r,'source':'unverified_rss_copy'}),[])
self.assertEqual(identities_for_row({**r,'identifiers':{'doi':'not-a-doi'}}),[])
def test_publisher_and_bibliography_both_orders_preserve_two_observations(self):
a=rss('rss_proceedings','2026/p041');b=rss('dblp','conf/rss/Example26')
for pair in ((a,b),(b,a)):
with self.subTest(first=pair[0]['source']),tempfile.TemporaryDirectory() as tmp:
root=Path(tmp);db=root/'papers.sqlite'
for r in pair:ingest(db,root,r)
ingest(db,root,a)
with sqlite3.connect(db) as c:
self.assertEqual(c.execute('SELECT count(*) FROM papers').fetchone()[0],1)
self.assertEqual(c.execute('SELECT count(*) FROM submissions').fetchone()[0],1)
self.assertEqual(c.execute('SELECT count(*) FROM source_records').fetchone()[0],2)
self.assertEqual(c.execute('SELECT count(*) FROM submission_sources').fetchone()[0],2)
self.assertEqual(c.execute('SELECT count(*) FROM decisions').fetchone()[0],2)
self.assertEqual(c.execute('SELECT count(*) FROM decision_sources').fetchone()[0],2)
self.assertEqual(c.execute('SELECT scheme,value FROM submission_identifiers').fetchall(),[('publication_doi',DOI)])
metadata=[json.loads(r[0]) for r in c.execute('SELECT metadata_json FROM source_records')]
self.assertEqual({x['original_source'] for x in metadata},{'rss_proceedings','dblp'})
self.assertEqual(c.execute('PRAGMA foreign_key_check').fetchall(),[])
def test_prior_rejection_remains_independent_and_raw_evidence_unchanged(self):
with tempfile.TemporaryDirectory() as tmp:
root=Path(tmp);db=root/'papers.sqlite'
old=row('openreview_snapshot','old-forum',venue='ICLR',year=2025,track='main',
status='rejected',identifiers={'arxiv':'2501.00001','openreview':'old-forum'})
ingest(db,root,old)
with sqlite3.connect(db) as c:
before=c.execute('SELECT * FROM decisions').fetchall()
new=rss('rss_proceedings','2026/p041',identifiers={'arxiv':'2501.00001','doi':DOI})
ingest(db,root,new)
with sqlite3.connect(db) as c:
self.assertEqual(c.execute('SELECT * FROM decisions WHERE status=\'rejected\'').fetchall(),before)
self.assertEqual(c.execute('SELECT count(*) FROM submissions').fetchone()[0],2)
self.assertEqual(c.execute('SELECT count(*) FROM papers').fetchone()[0],1)
self.assertEqual(c.execute('SELECT count(*) FROM decisions WHERE status=\'accepted\'').fetchone()[0],1)
self.assertEqual(c.execute('PRAGMA foreign_key_check').fetchall(),[])