feat(submissions): add multilingual document storage for publication packages
Extends SubmissionTracking model to support complete bilingual submission packages with version control for multiple languages. Schema additions: - documents.coverLetter.versions[] - Language-versioned content - documents.mainArticle.versions[] - With translation metadata - documents.authorBio.versions[] - documents.technicalBrief.versions[] Helper methods: - getDocument(docType, language, fallbackToDefault) - setDocumentVersion(docType, language, content, metadata) - getAvailableLanguages(docType) - isPackageComplete(language) - exportPackage(language) Scripts: - load-lemonde-package.js - Loads complete Le Monde submission package Le Monde Package: - Publication target: Rank 10, high-value French intellectual publication - Theme: Post-Weberian organizational theory for AI age - Content: Wittgenstein + Weber critique + indigenous data sovereignty - Format: 187-word letter (within 150-200 requirement) - Languages: English (original) + French (translated) - Database ID: 68fa2abd2e6acd5691932150 - Status: Ready for submission 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d7d317bb3b
commit
064cb4b8ae
2 changed files with 313 additions and 1 deletions
142
scripts/load-lemonde-package.js
Executable file
142
scripts/load-lemonde-package.js
Executable file
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Load Le Monde Submission Package
|
||||
* Stores the complete bilingual submission package in the database
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const mongoose = require('mongoose');
|
||||
require('dotenv').config();
|
||||
|
||||
const SubmissionTracking = require('../src/models/SubmissionTracking.model');
|
||||
|
||||
const PACKAGE_DIR = '/tmp/le-monde-package';
|
||||
|
||||
// Document file mappings
|
||||
const DOCUMENTS = {
|
||||
coverLetter: {
|
||||
en: '1-cover-letter.txt',
|
||||
fr: '1-cover-letter-FR.txt'
|
||||
},
|
||||
mainArticle: {
|
||||
en: '2-letter-to-editor.txt',
|
||||
fr: '2-letter-to-editor-FR.txt'
|
||||
},
|
||||
authorBio: {
|
||||
en: '3-author-bio.txt',
|
||||
fr: '3-author-bio-FR.txt'
|
||||
},
|
||||
technicalBrief: {
|
||||
en: '4-technical-brief.txt',
|
||||
fr: '4-technical-brief-FR.txt'
|
||||
}
|
||||
};
|
||||
|
||||
async function loadPackage() {
|
||||
try {
|
||||
// Connect to database
|
||||
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/tractatus_dev';
|
||||
await mongoose.connect(mongoUri);
|
||||
console.log('✓ Connected to MongoDB');
|
||||
|
||||
// Check if package already exists
|
||||
let submission = await SubmissionTracking.findOne({
|
||||
publicationId: 'le-monde-lettre',
|
||||
title: 'Beyond Weber: AI and the Limits of Bureaucratic Rationality'
|
||||
});
|
||||
|
||||
if (!submission) {
|
||||
// Create new submission tracking entry
|
||||
submission = new SubmissionTracking({
|
||||
publicationId: 'le-monde-lettre',
|
||||
publicationName: 'Le Monde',
|
||||
title: 'Beyond Weber: AI and the Limits of Bureaucratic Rationality',
|
||||
wordCount: 187,
|
||||
contentType: 'letter',
|
||||
status: 'ready',
|
||||
createdBy: new mongoose.Types.ObjectId('000000000000000000000000'), // System user
|
||||
lastUpdatedBy: new mongoose.Types.ObjectId('000000000000000000000000')
|
||||
});
|
||||
console.log('✓ Created new submission tracking entry');
|
||||
} else {
|
||||
console.log('✓ Found existing submission tracking entry');
|
||||
}
|
||||
|
||||
// Load and store each document type in both languages
|
||||
for (const [docType, files] of Object.entries(DOCUMENTS)) {
|
||||
console.log(`\nProcessing ${docType}...`);
|
||||
|
||||
for (const [language, filename] of Object.entries(files)) {
|
||||
const filepath = path.join(PACKAGE_DIR, filename);
|
||||
|
||||
if (!fs.existsSync(filepath)) {
|
||||
console.warn(` ⚠ File not found: ${filepath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filepath, 'utf8');
|
||||
const wordCount = content.split(/\s+/).length;
|
||||
|
||||
await submission.setDocumentVersion(docType, language, content, {
|
||||
translatedBy: language === 'en' ? 'manual' : 'claude',
|
||||
approved: true
|
||||
});
|
||||
|
||||
console.log(` ✓ Loaded ${language}: ${wordCount} words`);
|
||||
}
|
||||
|
||||
// Mark document type as completed
|
||||
if (submission.documents[docType]) {
|
||||
submission.documents[docType].completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save final state
|
||||
await submission.save();
|
||||
|
||||
// Display summary
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('PACKAGE SUMMARY');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Publication: ${submission.publicationName} (${submission.publicationId})`);
|
||||
console.log(`Title: ${submission.title}`);
|
||||
console.log(`Status: ${submission.status}`);
|
||||
console.log(`\nDocuments:`);
|
||||
|
||||
for (const docType of ['coverLetter', 'mainArticle', 'authorBio', 'technicalBrief']) {
|
||||
const languages = submission.getAvailableLanguages(docType);
|
||||
const completed = submission.documents[docType]?.completed ? '✓' : '✗';
|
||||
console.log(` ${completed} ${docType}: ${languages.join(', ')}`);
|
||||
}
|
||||
|
||||
console.log(`\nPackage complete (EN): ${submission.isPackageComplete('en')}`);
|
||||
console.log(`Package complete (FR): ${submission.isPackageComplete('fr')}`);
|
||||
|
||||
// Export sample
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('FRENCH PACKAGE EXPORT (SAMPLE)');
|
||||
console.log('='.repeat(60));
|
||||
const frPackage = submission.exportPackage('fr');
|
||||
console.log(`\nCover Letter (${frPackage.documents.coverLetter.wordCount} words):`);
|
||||
console.log(frPackage.documents.coverLetter.content.substring(0, 200) + '...\n');
|
||||
|
||||
console.log(`Main Article (${frPackage.documents.mainArticle.wordCount} words):`);
|
||||
console.log(frPackage.documents.mainArticle.content.substring(0, 200) + '...\n');
|
||||
|
||||
console.log(`\n✅ Le Monde submission package loaded successfully!`);
|
||||
console.log(`\nSubmission ID: ${submission._id}`);
|
||||
|
||||
await mongoose.connection.close();
|
||||
} catch (error) {
|
||||
console.error('❌ Error loading package:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run if called directly
|
||||
if (require.main === module) {
|
||||
loadPackage();
|
||||
}
|
||||
|
||||
module.exports = { loadPackage };
|
||||
|
|
@ -10,7 +10,7 @@ const SubmissionTrackingSchema = new mongoose.Schema({
|
|||
blogPostId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'BlogPost',
|
||||
required: true,
|
||||
required: false, // Optional for standalone submission packages
|
||||
index: true
|
||||
},
|
||||
publicationId: {
|
||||
|
|
@ -129,6 +129,65 @@ const SubmissionTrackingSchema = new mongoose.Schema({
|
|||
}]
|
||||
},
|
||||
|
||||
// Multilingual document storage for complete submission packages
|
||||
documents: {
|
||||
coverLetter: {
|
||||
primaryLanguage: { type: String, default: 'en' },
|
||||
versions: [{
|
||||
language: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['en', 'fr', 'de', 'es', 'pt', 'zh', 'ja', 'ar', 'mi']
|
||||
},
|
||||
content: String,
|
||||
wordCount: Number,
|
||||
lastUpdated: { type: Date, default: Date.now },
|
||||
translatedBy: {
|
||||
type: String,
|
||||
enum: ['human', 'deepl', 'claude', 'google', 'manual']
|
||||
},
|
||||
approved: { type: Boolean, default: false }
|
||||
}],
|
||||
completed: { type: Boolean, default: false }
|
||||
},
|
||||
mainArticle: {
|
||||
primaryLanguage: { type: String, default: 'en' },
|
||||
versions: [{
|
||||
language: String,
|
||||
content: String,
|
||||
wordCount: Number,
|
||||
lastUpdated: { type: Date, default: Date.now },
|
||||
translatedBy: String,
|
||||
approved: { type: Boolean, default: false }
|
||||
}],
|
||||
completed: { type: Boolean, default: false }
|
||||
},
|
||||
authorBio: {
|
||||
primaryLanguage: { type: String, default: 'en' },
|
||||
versions: [{
|
||||
language: String,
|
||||
content: String,
|
||||
wordCount: Number,
|
||||
lastUpdated: { type: Date, default: Date.now },
|
||||
translatedBy: String,
|
||||
approved: { type: Boolean, default: false }
|
||||
}],
|
||||
completed: { type: Boolean, default: false }
|
||||
},
|
||||
technicalBrief: {
|
||||
primaryLanguage: { type: String, default: 'en' },
|
||||
versions: [{
|
||||
language: String,
|
||||
content: String,
|
||||
wordCount: Number,
|
||||
lastUpdated: { type: Date, default: Date.now },
|
||||
translatedBy: String,
|
||||
approved: { type: Boolean, default: false }
|
||||
}],
|
||||
completed: { type: Boolean, default: false }
|
||||
}
|
||||
},
|
||||
|
||||
// Metadata
|
||||
createdBy: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
|
|
@ -313,6 +372,117 @@ SubmissionTrackingSchema.methods.addNote = async function(content, authorId) {
|
|||
return await this.save();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get document in specific language (with fallback)
|
||||
*/
|
||||
SubmissionTrackingSchema.methods.getDocument = function(docType, language, fallbackToDefault = true) {
|
||||
const doc = this.documents?.[docType];
|
||||
if (!doc) return null;
|
||||
|
||||
// Find version in requested language
|
||||
let version = doc.versions.find(v => v.language === language);
|
||||
|
||||
// Fallback to primary language if requested
|
||||
if (!version && fallbackToDefault) {
|
||||
version = doc.versions.find(v => v.language === doc.primaryLanguage);
|
||||
}
|
||||
|
||||
return version;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add or update document version
|
||||
*/
|
||||
SubmissionTrackingSchema.methods.setDocumentVersion = async function(docType, language, content, metadata = {}) {
|
||||
if (!this.documents) {
|
||||
this.documents = {};
|
||||
}
|
||||
|
||||
if (!this.documents[docType]) {
|
||||
this.documents[docType] = {
|
||||
primaryLanguage: language,
|
||||
versions: [],
|
||||
completed: false
|
||||
};
|
||||
}
|
||||
|
||||
// Find existing version or create new
|
||||
const existingIndex = this.documents[docType].versions.findIndex(v => v.language === language);
|
||||
|
||||
const versionData = {
|
||||
language,
|
||||
content,
|
||||
wordCount: content.split(/\s+/).length,
|
||||
lastUpdated: new Date(),
|
||||
...metadata
|
||||
};
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
this.documents[docType].versions[existingIndex] = {
|
||||
...this.documents[docType].versions[existingIndex],
|
||||
...versionData
|
||||
};
|
||||
} else {
|
||||
this.documents[docType].versions.push(versionData);
|
||||
}
|
||||
|
||||
return await this.save();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all available languages for a document type
|
||||
*/
|
||||
SubmissionTrackingSchema.methods.getAvailableLanguages = function(docType) {
|
||||
const doc = this.documents?.[docType];
|
||||
if (!doc) return [];
|
||||
return doc.versions.map(v => v.language);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if submission package is complete for a language
|
||||
*/
|
||||
SubmissionTrackingSchema.methods.isPackageComplete = function(language) {
|
||||
const requiredDocs = ['coverLetter', 'mainArticle', 'authorBio'];
|
||||
|
||||
return requiredDocs.every(docType => {
|
||||
const doc = this.documents?.[docType];
|
||||
if (!doc) return false;
|
||||
return doc.versions.some(v => v.language === language);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Export package for submission
|
||||
*/
|
||||
SubmissionTrackingSchema.methods.exportPackage = function(language) {
|
||||
const packageData = {
|
||||
publication: {
|
||||
id: this.publicationId,
|
||||
name: this.publicationName
|
||||
},
|
||||
metadata: {
|
||||
title: this.title,
|
||||
wordCount: this.wordCount,
|
||||
language,
|
||||
exportedAt: new Date()
|
||||
},
|
||||
documents: {}
|
||||
};
|
||||
|
||||
['coverLetter', 'mainArticle', 'authorBio', 'technicalBrief'].forEach(docType => {
|
||||
const version = this.getDocument(docType, language, true);
|
||||
if (version) {
|
||||
packageData.documents[docType] = {
|
||||
content: version.content,
|
||||
wordCount: version.wordCount,
|
||||
language: version.language
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return packageData;
|
||||
};
|
||||
|
||||
const SubmissionTracking = mongoose.model('SubmissionTracking', SubmissionTrackingSchema);
|
||||
|
||||
module.exports = SubmissionTracking;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue