aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
335 lines (287 loc) • 9.08 kB
text/typescript
/**
* Citation service for formatting and validating citations
*
* @module research/services/citation
*/
import { promises as fs } from 'fs';
import { ResearchPaper } from '../types.js';
import { CitationStyle, CitationNetwork } from './types.js';
/**
* Configuration for citation service
*/
export interface CitationConfig {
/** Corpus directory for validation */
corpusPath?: string;
/** Claims index path */
claimsIndexPath?: string;
}
/**
* Citation service for managing citations and networks
*/
export class CitationService {
private corpusPath: string;
private claimsIndexPath: string;
constructor(config: CitationConfig = {}) {
this.corpusPath = config.corpusPath || '.aiwg/research/sources';
this.claimsIndexPath = config.claimsIndexPath || '.aiwg/research/claims-index.json';
}
/**
* Format citation in specified style
*/
formatCitation(paper: ResearchPaper, style: CitationStyle = 'apa'): string {
switch (style) {
case 'apa':
return this.formatAPA(paper);
case 'bibtex':
return this.formatBibTeX(paper);
case 'chicago':
return this.formatChicago(paper);
case 'mla':
return this.formatMLA(paper);
case 'ieee':
return this.formatIEEE(paper);
default:
return this.formatAPA(paper);
}
}
/**
* Validate that a citation exists in corpus
*/
async validateCitation(refId: string, corpusPath?: string): Promise<boolean> {
const path = corpusPath || this.corpusPath;
try {
const files = await fs.readdir(path);
return files.some((file) => file.startsWith(refId));
} catch (error) {
return false;
}
}
/**
* Build citation network from papers
*/
buildNetwork(papers: ResearchPaper[]): CitationNetwork {
const nodes = papers.map((paper) => ({
refId: paper.id,
title: paper.title,
year: paper.year,
citationCount: paper.citationCount || 0,
}));
// For now, return empty edges (full implementation would extract references)
const edges: Array<{
source: string;
target: string;
type: 'direct' | 'co-citation';
}> = [];
// Calculate simple metrics
const sortedByCitations = [...nodes].sort(
(a, b) => b.citationCount - a.citationCount
);
const mostCitedPapers = sortedByCitations.slice(0, 5).map((n) => n.refId);
return {
nodes,
edges,
metrics: {
mostCitedPapers,
mostInfluentialPapers: mostCitedPapers, // Simplified
clusterCenters: [], // Would require clustering algorithm
},
};
}
/**
* Update claims index with new claim
*/
async updateClaimsIndex(claim: string, sources: string[]): Promise<void> {
let index: Record<string, string[]> = {};
// Load existing index
try {
const data = await fs.readFile(this.claimsIndexPath, 'utf-8');
index = JSON.parse(data);
} catch (error) {
// Index doesn't exist yet
}
// Update or create claim entry
if (index[claim]) {
// Merge sources, remove duplicates
const merged = [...new Set([...index[claim], ...sources])];
index[claim] = merged;
} else {
index[claim] = sources;
}
// Write back
await fs.writeFile(
this.claimsIndexPath,
JSON.stringify(index, null, 2),
'utf-8'
);
}
/**
* Format citation in APA style
*/
private formatAPA(paper: ResearchPaper): string {
const authors = this.formatAuthorsAPA(paper.authors.map((a) => a.name));
const year = paper.year || 'n.d.';
const title = paper.title;
const venue = paper.venue || '';
const doi = paper.doi ? `https://doi.org/${paper.doi}` : '';
let citation = `${authors} (${year}). ${title}.`;
if (venue) {
citation += ` ${venue}.`;
}
if (doi) {
citation += ` ${doi}`;
}
return citation;
}
/**
* Format citation in BibTeX style
*/
private formatBibTeX(paper: ResearchPaper): string {
const id = this.generateBibTeXId(paper);
const authors = paper.authors.map((a) => a.name).join(' and ');
const year = paper.year || '';
const title = paper.title;
const venue = paper.venue || '';
const doi = paper.doi || '';
const entryType = paper.type === 'journal' ? 'article' : 'inproceedings';
let bibtex = `@${entryType}{${id},\n`;
bibtex += ` author = {${authors}},\n`;
bibtex += ` title = {${title}},\n`;
bibtex += ` year = {${year}},\n`;
if (venue) {
const field = entryType === 'article' ? 'journal' : 'booktitle';
bibtex += ` ${field} = {${venue}},\n`;
}
if (doi) {
bibtex += ` doi = {${doi}},\n`;
}
bibtex += `}`;
return bibtex;
}
/**
* Format citation in Chicago style
*/
private formatChicago(paper: ResearchPaper): string {
const authors = this.formatAuthorsChicago(
paper.authors.map((a) => a.name)
);
const year = paper.year || 'n.d.';
const title = `"${paper.title}"`;
const venue = paper.venue || '';
const doi = paper.doi ? `https://doi.org/${paper.doi}` : '';
let citation = `${authors}. ${year}. ${title}.`;
if (venue) {
citation += ` ${venue}.`;
}
if (doi) {
citation += ` ${doi}`;
}
return citation;
}
/**
* Format citation in MLA style
*/
private formatMLA(paper: ResearchPaper): string {
const authors = this.formatAuthorsMLA(paper.authors.map((a) => a.name));
const title = `"${paper.title}"`;
const venue = paper.venue || '';
const year = paper.year || 'n.d.';
let citation = `${authors}. ${title}.`;
if (venue) {
citation += ` ${venue},`;
}
citation += ` ${year}.`;
return citation;
}
/**
* Format citation in IEEE style
*/
private formatIEEE(paper: ResearchPaper): string {
const authors = this.formatAuthorsIEEE(paper.authors.map((a) => a.name));
const title = `"${paper.title}"`;
const venue = paper.venue ? `, ${paper.venue}` : '';
const year = paper.year || 'n.d.';
return `${authors}, ${title}${venue}, ${year}.`;
}
/**
* Format authors in APA style
*/
private formatAuthorsAPA(authors: string[]): string {
if (authors.length === 0) return 'Unknown';
if (authors.length === 1) return authors[0];
if (authors.length === 2) return `${authors[0]} & ${authors[1]}`;
const lastAuthor = authors[authors.length - 1];
const otherAuthors = authors.slice(0, -1).join(', ');
return `${otherAuthors}, & ${lastAuthor}`;
}
/**
* Format authors in Chicago style
*/
private formatAuthorsChicago(authors: string[]): string {
if (authors.length === 0) return 'Unknown';
if (authors.length === 1) return this.reverseAuthorName(authors[0]);
if (authors.length === 2) {
return `${this.reverseAuthorName(authors[0])} and ${authors[1]}`;
}
const firstAuthor = this.reverseAuthorName(authors[0]);
const otherAuthors = authors.slice(1).join(', ');
return `${firstAuthor}, ${otherAuthors}`;
}
/**
* Format authors in MLA style
*/
private formatAuthorsMLA(authors: string[]): string {
if (authors.length === 0) return 'Unknown';
if (authors.length === 1) return this.reverseAuthorName(authors[0]);
if (authors.length === 2) {
return `${this.reverseAuthorName(authors[0])}, and ${authors[1]}`;
}
const firstAuthor = this.reverseAuthorName(authors[0]);
return `${firstAuthor}, et al.`;
}
/**
* Format authors in IEEE style
*/
private formatAuthorsIEEE(authors: string[]): string {
if (authors.length === 0) return 'Unknown';
const initials = authors.map((name) => {
const parts = name.split(' ');
if (parts.length === 1) return name;
const lastName = parts[parts.length - 1];
const firstInitials = parts
.slice(0, -1)
.map((p) => p.charAt(0) + '.')
.join(' ');
return `${firstInitials} ${lastName}`;
});
if (initials.length === 1) return initials[0];
if (initials.length === 2) return `${initials[0]} and ${initials[1]}`;
const lastAuthor = initials[initials.length - 1];
const otherAuthors = initials.slice(0, -1).join(', ');
return `${otherAuthors}, and ${lastAuthor}`;
}
/**
* Reverse author name (First Last -> Last, First)
*/
private reverseAuthorName(name: string): string {
const parts = name.split(' ');
if (parts.length === 1) return name;
const lastName = parts[parts.length - 1];
const firstName = parts.slice(0, -1).join(' ');
return `${lastName}, ${firstName}`;
}
/**
* Generate BibTeX citation ID
*/
private generateBibTeXId(paper: ResearchPaper): string {
const author =
paper.authors.length > 0
? paper.authors[0].name.split(' ').pop()?.toLowerCase()
: 'unknown';
const year = paper.year || 'unknown';
const titleWord = paper.title
.toLowerCase()
.replace(/[^\w\s]/g, '')
.split(/\s+/)[0];
return `${author}${year}${titleWord}`;
}
}