参考文献作者堆叠

参考文献相关性检测
作者ai写作辅助检测工作
This commit is contained in:
wyn
2026-07-15 10:49:05 +08:00
parent da71dfc04e
commit 8785610e6d
27 changed files with 10008 additions and 204 deletions

View File

@@ -104,6 +104,64 @@ class BackgroundCheckService
return ['count' => count($list), 'list' => $list, 'source' => 'openalex'];
}
/**
* 按 DOI 从 OpenAlex 获取文献及作者身份(用于引用堆叠精准作者定位)
*/
public function fetchOpenAlexWorkByDoi($doi)
{
$doi = $this->cleanDoi($doi);
if ($doi === '') {
return ['success' => false, 'error' => 'DOI为空'];
}
$res = $this->openAlexGet('/works/https://doi.org/' . rawurlencode($doi));
if (!$res['success']) {
return $res;
}
return ['success' => true, 'work' => $res['data']];
}
/**
* 解析 OpenAlex work 中的作者身份OpenAlex ID + ORCID
*
* @return array<int, array{openalex_id:string,orcid:string,display_name:string,author_position:string,is_corresponding:bool,identity_keys:string[]}>
*/
public function parseWorkAuthorships(array $work)
{
$list = [];
foreach ($work['authorships'] ?? [] as $auth) {
if (!is_array($auth)) {
continue;
}
$author = is_array($auth['author'] ?? null) ? $auth['author'] : [];
$openalexId = $this->extractOpenAlexId($author['id'] ?? '');
$orcid = $this->cleanOrcid($author['orcid'] ?? '');
if ($openalexId === '' && $orcid === '') {
continue;
}
$identityKeys = [];
if ($openalexId !== '') {
$identityKeys[] = 'openalex:' . $openalexId;
}
if ($orcid !== '') {
$identityKeys[] = 'orcid:' . $orcid;
}
$list[] = [
'openalex_id' => $openalexId,
'orcid' => $orcid,
'display_name' => trim((string)($author['display_name'] ?? '')),
'author_position' => (string)($auth['author_position'] ?? ''),
'is_corresponding' => !empty($auth['is_corresponding']),
'identity_keys' => $identityKeys,
];
}
return $list;
}
public function fetchRecentWorks($openAlexId, $limit = 5)
{
$res = $this->openAlexGet('/works', [
@@ -604,7 +662,7 @@ class BackgroundCheckService
];
}
private function parseCrossRefAuthors($authorList)
public function parseCrossRefAuthors($authorList)
{
if (empty($authorList) || !is_array($authorList)) {
return [];
@@ -612,16 +670,52 @@ class BackgroundCheckService
$result = [];
foreach ($authorList as $a) {
$orcid = '';
if (!empty($a['ORCID']) && is_array($a['ORCID'])) {
$orcid = $this->cleanOrcid((string)($a['ORCID'][0] ?? ''));
} elseif (!empty($a['ORCID']) && is_string($a['ORCID'])) {
$orcid = $this->cleanOrcid($a['ORCID']);
}
$result[] = [
'given' => $a['given'] ?? '',
'family' => $a['family'] ?? '',
'name' => isset($a['name']) ? $a['name'] : trim(($a['given'] ?? '') . ' ' . ($a['family'] ?? '')),
'orcid' => $a['ORCID'] ?? '',
'orcid' => $orcid,
];
}
return $result;
}
/**
* Crossref 作者列表 → 带 identity_keys 的结构ORCID 兜底)
*
* @return array<int, array{openalex_id:string,orcid:string,display_name:string,author_position:string,is_corresponding:bool,identity_keys:string[]}>
*/
public function authorshipsFromCrossrefAuthors(array $authorList)
{
$parsed = $this->parseCrossRefAuthors($authorList);
$list = [];
$total = count($parsed);
foreach ($parsed as $i => $a) {
$orcid = trim((string)($a['orcid'] ?? ''));
if ($orcid === '') {
continue;
}
$displayName = trim((string)($a['name'] ?? ''));
$position = ($i === 0) ? 'first' : (($i === $total - 1) ? 'last' : 'middle');
$list[] = [
'openalex_id' => '',
'orcid' => $orcid,
'display_name' => $displayName,
'author_position' => $position,
'is_corresponding' => false,
'identity_keys' => ['orcid:' . $orcid],
];
}
return $list;
}
private function parseDateParts($dateObj)
{
if (!isset($dateObj['date-parts'][0])) {