From 9edf5a5b23579850ead1b08893c36c676e451e01 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Mon, 9 Jun 2025 19:56:55 -0700 Subject: [PATCH] Added recursive pydantic model converter --- frontend/src/types/types.ts | 343 +++++++++++++++++++++++++--------- src/backend/generate_types.py | 280 ++++++++++++++++++++++----- 2 files changed, 488 insertions(+), 135 deletions(-) diff --git a/frontend/src/types/types.ts b/frontend/src/types/types.ts index 95b6691..c012bdf 100644 --- a/frontend/src/types/types.ts +++ b/frontend/src/types/types.ts @@ -1,6 +1,6 @@ // Generated TypeScript types from Pydantic models // Source: src/backend/models.py -// Generated on: 2025-06-09T20:36:06.432367 +// Generated on: 2025-06-10T02:48:12.087485 // DO NOT EDIT MANUALLY - This file is auto-generated // ============================ @@ -161,8 +161,8 @@ export interface BaseUser { fullName: string; phone?: string; location?: Location; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; lastLogin?: Date; profileImage?: string; status: "active" | "inactive" | "pending" | "banned"; @@ -185,8 +185,8 @@ export interface Candidate { fullName: string; phone?: string; location?: Location; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; lastLogin?: Date; profileImage?: string; status: "active" | "inactive" | "pending" | "banned"; @@ -219,8 +219,8 @@ export interface CandidateAI { fullName: string; phone?: string; location?: Location; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; lastLogin?: Date; profileImage?: string; status: "active" | "inactive" | "pending" | "banned"; @@ -353,6 +353,8 @@ export interface ChatMessageResume { tunables?: Tunables; metadata: ChatMessageMetaData; resume: string; + systemPrompt?: string; + prompt?: string; } export interface ChatMessageSkillAssessment { @@ -567,8 +569,8 @@ export interface Employer { fullName: string; phone?: string; location?: Location; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; lastLogin?: Date; profileImage?: string; status: "active" | "inactive" | "pending" | "banned"; @@ -620,8 +622,8 @@ export interface Guest { fullName: string; phone?: string; location?: Location; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; lastLogin?: Date; profileImage?: string; status: "active" | "inactive" | "pending" | "banned"; @@ -682,8 +684,8 @@ export interface InterviewFeedback { weaknesses: Array; recommendation: "strong_hire" | "hire" | "no_hire" | "strong_no_hire"; comments: string; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; isVisible: boolean; skillAssessments?: Array; } @@ -712,8 +714,8 @@ export interface Job { company?: string; description: string; requirements?: JobRequirements; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; } export interface JobApplication { @@ -742,12 +744,12 @@ export interface JobFull { company?: string; description: string; requirements?: JobRequirements; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; location: Location; salaryRange?: SalaryRange; employmentType: "full-time" | "part-time" | "contract" | "internship" | "freelance"; - datePosted: Date; + datePosted?: Date; applicationDeadline?: Date; isActive: boolean; applicants?: Array; @@ -784,11 +786,7 @@ export interface JobRequirementsMessage { status: "streaming" | "status" | "done" | "error"; type: "binary" | "text" | "json"; timestamp?: Date; - title?: string; - summary?: string; - company?: string; - description: string; - requirements?: JobRequirements; + job: Job; } export interface JobResponse { @@ -901,8 +899,8 @@ export interface RAGConfiguration { embeddingModel: string; vectorStoreType: "chroma"; retrievalParameters: RetrievalParameters; - createdAt: Date; - updatedAt: Date; + createdAt?: Date; + updatedAt?: Date; version: number; isActive: boolean; } @@ -1078,14 +1076,14 @@ export interface WorkExperience { } // ============================ -// Date Conversion Functions +// Date and Nested Model Conversion Functions // ============================ // These functions convert API responses to properly typed objects -// with Date objects instead of ISO date strings +// with Date objects instead of ISO date strings and nested model conversions /** - * Convert Analytics from API response, parsing date fields + * Convert Analytics from API response * Date fields: timestamp */ export function convertAnalyticsFromApi(data: any): Analytics { @@ -1098,7 +1096,7 @@ export function convertAnalyticsFromApi(data: any): Analytics { }; } /** - * Convert ApiMessage from API response, parsing date fields + * Convert ApiMessage from API response * Date fields: timestamp */ export function convertApiMessageFromApi(data: any): ApiMessage { @@ -1111,7 +1109,7 @@ export function convertApiMessageFromApi(data: any): ApiMessage { }; } /** - * Convert ApplicationDecision from API response, parsing date fields + * Convert ApplicationDecision from API response * Date fields: date */ export function convertApplicationDecisionFromApi(data: any): ApplicationDecision { @@ -1124,7 +1122,7 @@ export function convertApplicationDecisionFromApi(data: any): ApplicationDecisio }; } /** - * Convert Attachment from API response, parsing date fields + * Convert Attachment from API response * Date fields: uploadedAt */ export function convertAttachmentFromApi(data: any): Attachment { @@ -1137,8 +1135,22 @@ export function convertAttachmentFromApi(data: any): Attachment { }; } /** - * Convert Authentication from API response, parsing date fields + * Convert AuthResponse from API response + * Nested models: user (Candidate) + */ +export function convertAuthResponseFromApi(data: any): AuthResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Candidate model + user: convertCandidateFromApi(data.user), + }; +} +/** + * Convert Authentication from API response * Date fields: resetPasswordExpiry, lastPasswordChange, lockedUntil + * Nested models: refreshTokens (RefreshToken) */ export function convertAuthenticationFromApi(data: any): Authentication { if (!data) return data; @@ -1151,10 +1163,12 @@ export function convertAuthenticationFromApi(data: any): Authentication { lastPasswordChange: new Date(data.lastPasswordChange), // Convert lockedUntil from ISO string to Date lockedUntil: data.lockedUntil ? new Date(data.lockedUntil) : undefined, + // Convert nested RefreshToken model + refreshTokens: data.refreshTokens.map((item: any) => convertRefreshTokenFromApi(item)), }; } /** - * Convert BaseUser from API response, parsing date fields + * Convert BaseUser from API response * Date fields: lastActivity, createdAt, updatedAt, lastLogin */ export function convertBaseUserFromApi(data: any): BaseUser { @@ -1165,15 +1179,15 @@ export function convertBaseUserFromApi(data: any): BaseUser { // Convert lastActivity from ISO string to Date lastActivity: data.lastActivity ? new Date(data.lastActivity) : undefined, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, // Convert lastLogin from ISO string to Date lastLogin: data.lastLogin ? new Date(data.lastLogin) : undefined, }; } /** - * Convert BaseUserWithType from API response, parsing date fields + * Convert BaseUserWithType from API response * Date fields: lastActivity */ export function convertBaseUserWithTypeFromApi(data: any): BaseUserWithType { @@ -1186,8 +1200,9 @@ export function convertBaseUserWithTypeFromApi(data: any): BaseUserWithType { }; } /** - * Convert Candidate from API response, parsing date fields + * Convert Candidate from API response * Date fields: lastActivity, createdAt, updatedAt, lastLogin, availabilityDate + * Nested models: experience (WorkExperience), education (Education), certifications (Certification), jobApplications (JobApplication) */ export function convertCandidateFromApi(data: any): Candidate { if (!data) return data; @@ -1197,18 +1212,27 @@ export function convertCandidateFromApi(data: any): Candidate { // Convert lastActivity from ISO string to Date lastActivity: data.lastActivity ? new Date(data.lastActivity) : undefined, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, // Convert lastLogin from ISO string to Date lastLogin: data.lastLogin ? new Date(data.lastLogin) : undefined, // Convert availabilityDate from ISO string to Date availabilityDate: data.availabilityDate ? new Date(data.availabilityDate) : undefined, + // Convert nested WorkExperience model + experience: data.experience ? convertWorkExperienceFromApi(data.experience) : undefined, + // Convert nested Education model + education: data.education ? convertEducationFromApi(data.education) : undefined, + // Convert nested Certification model + certifications: data.certifications ? convertCertificationFromApi(data.certifications) : undefined, + // Convert nested JobApplication model + jobApplications: data.jobApplications ? convertJobApplicationFromApi(data.jobApplications) : undefined, }; } /** - * Convert CandidateAI from API response, parsing date fields + * Convert CandidateAI from API response * Date fields: lastActivity, createdAt, updatedAt, lastLogin, availabilityDate + * Nested models: experience (WorkExperience), education (Education), certifications (Certification) */ export function convertCandidateAIFromApi(data: any): CandidateAI { if (!data) return data; @@ -1218,17 +1242,49 @@ export function convertCandidateAIFromApi(data: any): CandidateAI { // Convert lastActivity from ISO string to Date lastActivity: data.lastActivity ? new Date(data.lastActivity) : undefined, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, // Convert lastLogin from ISO string to Date lastLogin: data.lastLogin ? new Date(data.lastLogin) : undefined, // Convert availabilityDate from ISO string to Date availabilityDate: data.availabilityDate ? new Date(data.availabilityDate) : undefined, + // Convert nested WorkExperience model + experience: data.experience ? convertWorkExperienceFromApi(data.experience) : undefined, + // Convert nested Education model + education: data.education ? convertEducationFromApi(data.education) : undefined, + // Convert nested Certification model + certifications: data.certifications ? convertCertificationFromApi(data.certifications) : undefined, }; } /** - * Convert Certification from API response, parsing date fields + * Convert CandidateListResponse from API response + * Nested models: data (Candidate) + */ +export function convertCandidateListResponseFromApi(data: any): CandidateListResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Candidate model + data: data.data ? convertCandidateFromApi(data.data) : undefined, + }; +} +/** + * Convert CandidateResponse from API response + * Nested models: data (Candidate) + */ +export function convertCandidateResponseFromApi(data: any): CandidateResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Candidate model + data: data.data ? convertCandidateFromApi(data.data) : undefined, + }; +} +/** + * Convert Certification from API response * Date fields: issueDate, expirationDate */ export function convertCertificationFromApi(data: any): Certification { @@ -1243,7 +1299,7 @@ export function convertCertificationFromApi(data: any): Certification { }; } /** - * Convert ChatMessage from API response, parsing date fields + * Convert ChatMessage from API response * Date fields: timestamp */ export function convertChatMessageFromApi(data: any): ChatMessage { @@ -1256,7 +1312,7 @@ export function convertChatMessageFromApi(data: any): ChatMessage { }; } /** - * Convert ChatMessageError from API response, parsing date fields + * Convert ChatMessageError from API response * Date fields: timestamp */ export function convertChatMessageErrorFromApi(data: any): ChatMessageError { @@ -1269,7 +1325,7 @@ export function convertChatMessageErrorFromApi(data: any): ChatMessageError { }; } /** - * Convert ChatMessageRagSearch from API response, parsing date fields + * Convert ChatMessageRagSearch from API response * Date fields: timestamp */ export function convertChatMessageRagSearchFromApi(data: any): ChatMessageRagSearch { @@ -1282,7 +1338,7 @@ export function convertChatMessageRagSearchFromApi(data: any): ChatMessageRagSea }; } /** - * Convert ChatMessageResume from API response, parsing date fields + * Convert ChatMessageResume from API response * Date fields: timestamp */ export function convertChatMessageResumeFromApi(data: any): ChatMessageResume { @@ -1295,8 +1351,9 @@ export function convertChatMessageResumeFromApi(data: any): ChatMessageResume { }; } /** - * Convert ChatMessageSkillAssessment from API response, parsing date fields + * Convert ChatMessageSkillAssessment from API response * Date fields: timestamp + * Nested models: skillAssessment (SkillAssessment) */ export function convertChatMessageSkillAssessmentFromApi(data: any): ChatMessageSkillAssessment { if (!data) return data; @@ -1305,10 +1362,12 @@ export function convertChatMessageSkillAssessmentFromApi(data: any): ChatMessage ...data, // Convert timestamp from ISO string to Date timestamp: data.timestamp ? new Date(data.timestamp) : undefined, + // Convert nested SkillAssessment model + skillAssessment: convertSkillAssessmentFromApi(data.skillAssessment), }; } /** - * Convert ChatMessageStatus from API response, parsing date fields + * Convert ChatMessageStatus from API response * Date fields: timestamp */ export function convertChatMessageStatusFromApi(data: any): ChatMessageStatus { @@ -1321,7 +1380,7 @@ export function convertChatMessageStatusFromApi(data: any): ChatMessageStatus { }; } /** - * Convert ChatMessageStreaming from API response, parsing date fields + * Convert ChatMessageStreaming from API response * Date fields: timestamp */ export function convertChatMessageStreamingFromApi(data: any): ChatMessageStreaming { @@ -1334,7 +1393,7 @@ export function convertChatMessageStreamingFromApi(data: any): ChatMessageStream }; } /** - * Convert ChatMessageUser from API response, parsing date fields + * Convert ChatMessageUser from API response * Date fields: timestamp */ export function convertChatMessageUserFromApi(data: any): ChatMessageUser { @@ -1347,7 +1406,7 @@ export function convertChatMessageUserFromApi(data: any): ChatMessageUser { }; } /** - * Convert ChatSession from API response, parsing date fields + * Convert ChatSession from API response * Date fields: createdAt, lastActivity */ export function convertChatSessionFromApi(data: any): ChatSession { @@ -1362,7 +1421,7 @@ export function convertChatSessionFromApi(data: any): ChatSession { }; } /** - * Convert DataSourceConfiguration from API response, parsing date fields + * Convert DataSourceConfiguration from API response * Date fields: lastRefreshed */ export function convertDataSourceConfigurationFromApi(data: any): DataSourceConfiguration { @@ -1375,7 +1434,7 @@ export function convertDataSourceConfigurationFromApi(data: any): DataSourceConf }; } /** - * Convert Document from API response, parsing date fields + * Convert Document from API response * Date fields: uploadDate */ export function convertDocumentFromApi(data: any): Document { @@ -1388,8 +1447,22 @@ export function convertDocumentFromApi(data: any): Document { }; } /** - * Convert DocumentMessage from API response, parsing date fields + * Convert DocumentListResponse from API response + * Nested models: documents (Document) + */ +export function convertDocumentListResponseFromApi(data: any): DocumentListResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Document model + documents: data.documents.map((item: any) => convertDocumentFromApi(item)), + }; +} +/** + * Convert DocumentMessage from API response * Date fields: timestamp + * Nested models: document (Document) */ export function convertDocumentMessageFromApi(data: any): DocumentMessage { if (!data) return data; @@ -1398,10 +1471,12 @@ export function convertDocumentMessageFromApi(data: any): DocumentMessage { ...data, // Convert timestamp from ISO string to Date timestamp: data.timestamp ? new Date(data.timestamp) : undefined, + // Convert nested Document model + document: convertDocumentFromApi(data.document), }; } /** - * Convert EditHistory from API response, parsing date fields + * Convert EditHistory from API response * Date fields: editedAt */ export function convertEditHistoryFromApi(data: any): EditHistory { @@ -1414,7 +1489,7 @@ export function convertEditHistoryFromApi(data: any): EditHistory { }; } /** - * Convert Education from API response, parsing date fields + * Convert Education from API response * Date fields: startDate, endDate */ export function convertEducationFromApi(data: any): Education { @@ -1429,8 +1504,9 @@ export function convertEducationFromApi(data: any): Education { }; } /** - * Convert Employer from API response, parsing date fields + * Convert Employer from API response * Date fields: lastActivity, createdAt, updatedAt, lastLogin + * Nested models: jobs (Job) */ export function convertEmployerFromApi(data: any): Employer { if (!data) return data; @@ -1440,15 +1516,30 @@ export function convertEmployerFromApi(data: any): Employer { // Convert lastActivity from ISO string to Date lastActivity: data.lastActivity ? new Date(data.lastActivity) : undefined, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, // Convert lastLogin from ISO string to Date lastLogin: data.lastLogin ? new Date(data.lastLogin) : undefined, + // Convert nested Job model + jobs: data.jobs ? convertJobFromApi(data.jobs) : undefined, }; } /** - * Convert Guest from API response, parsing date fields + * Convert EmployerResponse from API response + * Nested models: data (Employer) + */ +export function convertEmployerResponseFromApi(data: any): EmployerResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Employer model + data: data.data ? convertEmployerFromApi(data.data) : undefined, + }; +} +/** + * Convert Guest from API response * Date fields: lastActivity, createdAt, updatedAt, lastLogin */ export function convertGuestFromApi(data: any): Guest { @@ -1459,16 +1550,30 @@ export function convertGuestFromApi(data: any): Guest { // Convert lastActivity from ISO string to Date lastActivity: data.lastActivity ? new Date(data.lastActivity) : undefined, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, // Convert lastLogin from ISO string to Date lastLogin: data.lastLogin ? new Date(data.lastLogin) : undefined, }; } /** - * Convert InterviewFeedback from API response, parsing date fields + * Convert GuestSessionResponse from API response + * Nested models: user (Guest) + */ +export function convertGuestSessionResponseFromApi(data: any): GuestSessionResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Guest model + user: convertGuestFromApi(data.user), + }; +} +/** + * Convert InterviewFeedback from API response * Date fields: createdAt, updatedAt + * Nested models: skillAssessments (SkillAssessment) */ export function convertInterviewFeedbackFromApi(data: any): InterviewFeedback { if (!data) return data; @@ -1476,14 +1581,17 @@ export function convertInterviewFeedbackFromApi(data: any): InterviewFeedback { return { ...data, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, + // Convert nested SkillAssessment model + skillAssessments: data.skillAssessments ? convertSkillAssessmentFromApi(data.skillAssessments) : undefined, }; } /** - * Convert InterviewSchedule from API response, parsing date fields + * Convert InterviewSchedule from API response * Date fields: scheduledDate, endDate + * Nested models: feedback (InterviewFeedback) */ export function convertInterviewScheduleFromApi(data: any): InterviewSchedule { if (!data) return data; @@ -1494,11 +1602,14 @@ export function convertInterviewScheduleFromApi(data: any): InterviewSchedule { scheduledDate: new Date(data.scheduledDate), // Convert endDate from ISO string to Date endDate: new Date(data.endDate), + // Convert nested InterviewFeedback model + feedback: data.feedback ? convertInterviewFeedbackFromApi(data.feedback) : undefined, }; } /** - * Convert Job from API response, parsing date fields + * Convert Job from API response * Date fields: createdAt, updatedAt + * Nested models: owner (BaseUser) */ export function convertJobFromApi(data: any): Job { if (!data) return data; @@ -1506,14 +1617,17 @@ export function convertJobFromApi(data: any): Job { return { ...data, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, + // Convert nested BaseUser model + owner: data.owner ? convertBaseUserFromApi(data.owner) : undefined, }; } /** - * Convert JobApplication from API response, parsing date fields + * Convert JobApplication from API response * Date fields: appliedDate, updatedDate + * Nested models: interviewSchedules (InterviewSchedule), decision (ApplicationDecision) */ export function convertJobApplicationFromApi(data: any): JobApplication { if (!data) return data; @@ -1524,11 +1638,16 @@ export function convertJobApplicationFromApi(data: any): JobApplication { appliedDate: new Date(data.appliedDate), // Convert updatedDate from ISO string to Date updatedDate: new Date(data.updatedDate), + // Convert nested InterviewSchedule model + interviewSchedules: data.interviewSchedules ? convertInterviewScheduleFromApi(data.interviewSchedules) : undefined, + // Convert nested ApplicationDecision model + decision: data.decision ? convertApplicationDecisionFromApi(data.decision) : undefined, }; } /** - * Convert JobFull from API response, parsing date fields + * Convert JobFull from API response * Date fields: createdAt, updatedAt, datePosted, applicationDeadline, featuredUntil + * Nested models: owner (BaseUser), applicants (JobApplication) */ export function convertJobFullFromApi(data: any): JobFull { if (!data) return data; @@ -1536,20 +1655,38 @@ export function convertJobFullFromApi(data: any): JobFull { return { ...data, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, // Convert datePosted from ISO string to Date - datePosted: new Date(data.datePosted), + datePosted: data.datePosted ? new Date(data.datePosted) : undefined, // Convert applicationDeadline from ISO string to Date applicationDeadline: data.applicationDeadline ? new Date(data.applicationDeadline) : undefined, // Convert featuredUntil from ISO string to Date featuredUntil: data.featuredUntil ? new Date(data.featuredUntil) : undefined, + // Convert nested BaseUser model + owner: data.owner ? convertBaseUserFromApi(data.owner) : undefined, + // Convert nested JobApplication model + applicants: data.applicants ? convertJobApplicationFromApi(data.applicants) : undefined, }; } /** - * Convert JobRequirementsMessage from API response, parsing date fields + * Convert JobListResponse from API response + * Nested models: data (Job) + */ +export function convertJobListResponseFromApi(data: any): JobListResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Job model + data: data.data ? convertJobFromApi(data.data) : undefined, + }; +} +/** + * Convert JobRequirementsMessage from API response * Date fields: timestamp + * Nested models: job (Job) */ export function convertJobRequirementsMessageFromApi(data: any): JobRequirementsMessage { if (!data) return data; @@ -1558,10 +1695,25 @@ export function convertJobRequirementsMessageFromApi(data: any): JobRequirements ...data, // Convert timestamp from ISO string to Date timestamp: data.timestamp ? new Date(data.timestamp) : undefined, + // Convert nested Job model + job: convertJobFromApi(data.job), }; } /** - * Convert MessageReaction from API response, parsing date fields + * Convert JobResponse from API response + * Nested models: data (Job) + */ +export function convertJobResponseFromApi(data: any): JobResponse { + if (!data) return data; + + return { + ...data, + // Convert nested Job model + data: data.data ? convertJobFromApi(data.data) : undefined, + }; +} +/** + * Convert MessageReaction from API response * Date fields: timestamp */ export function convertMessageReactionFromApi(data: any): MessageReaction { @@ -1574,8 +1726,9 @@ export function convertMessageReactionFromApi(data: any): MessageReaction { }; } /** - * Convert RAGConfiguration from API response, parsing date fields + * Convert RAGConfiguration from API response * Date fields: createdAt, updatedAt + * Nested models: dataSourceConfigurations (DataSourceConfiguration) */ export function convertRAGConfigurationFromApi(data: any): RAGConfiguration { if (!data) return data; @@ -1583,13 +1736,15 @@ export function convertRAGConfigurationFromApi(data: any): RAGConfiguration { return { ...data, // Convert createdAt from ISO string to Date - createdAt: new Date(data.createdAt), + createdAt: data.createdAt ? new Date(data.createdAt) : undefined, // Convert updatedAt from ISO string to Date - updatedAt: new Date(data.updatedAt), + updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined, + // Convert nested DataSourceConfiguration model + dataSourceConfigurations: data.dataSourceConfigurations.map((item: any) => convertDataSourceConfigurationFromApi(item)), }; } /** - * Convert RateLimitResult from API response, parsing date fields + * Convert RateLimitResult from API response * Date fields: resetTimes */ export function convertRateLimitResultFromApi(data: any): RateLimitResult { @@ -1602,7 +1757,7 @@ export function convertRateLimitResultFromApi(data: any): RateLimitResult { }; } /** - * Convert RateLimitStatus from API response, parsing date fields + * Convert RateLimitStatus from API response * Date fields: resetTimes */ export function convertRateLimitStatusFromApi(data: any): RateLimitStatus { @@ -1615,7 +1770,7 @@ export function convertRateLimitStatusFromApi(data: any): RateLimitStatus { }; } /** - * Convert RefreshToken from API response, parsing date fields + * Convert RefreshToken from API response * Date fields: expiresAt */ export function convertRefreshTokenFromApi(data: any): RefreshToken { @@ -1628,7 +1783,7 @@ export function convertRefreshTokenFromApi(data: any): RefreshToken { }; } /** - * Convert SkillAssessment from API response, parsing date fields + * Convert SkillAssessment from API response * Date fields: createdAt, updatedAt */ export function convertSkillAssessmentFromApi(data: any): SkillAssessment { @@ -1643,7 +1798,7 @@ export function convertSkillAssessmentFromApi(data: any): SkillAssessment { }; } /** - * Convert UserActivity from API response, parsing date fields + * Convert UserActivity from API response * Date fields: timestamp */ export function convertUserActivityFromApi(data: any): UserActivity { @@ -1656,7 +1811,7 @@ export function convertUserActivityFromApi(data: any): UserActivity { }; } /** - * Convert WorkExperience from API response, parsing date fields + * Convert WorkExperience from API response * Date fields: startDate, endDate */ export function convertWorkExperienceFromApi(data: any): WorkExperience { @@ -1687,6 +1842,8 @@ export function convertFromApi(data: any, modelType: string): T { return convertApplicationDecisionFromApi(data) as T; case 'Attachment': return convertAttachmentFromApi(data) as T; + case 'AuthResponse': + return convertAuthResponseFromApi(data) as T; case 'Authentication': return convertAuthenticationFromApi(data) as T; case 'BaseUser': @@ -1697,6 +1854,10 @@ export function convertFromApi(data: any, modelType: string): T { return convertCandidateFromApi(data) as T; case 'CandidateAI': return convertCandidateAIFromApi(data) as T; + case 'CandidateListResponse': + return convertCandidateListResponseFromApi(data) as T; + case 'CandidateResponse': + return convertCandidateResponseFromApi(data) as T; case 'Certification': return convertCertificationFromApi(data) as T; case 'ChatMessage': @@ -1721,6 +1882,8 @@ export function convertFromApi(data: any, modelType: string): T { return convertDataSourceConfigurationFromApi(data) as T; case 'Document': return convertDocumentFromApi(data) as T; + case 'DocumentListResponse': + return convertDocumentListResponseFromApi(data) as T; case 'DocumentMessage': return convertDocumentMessageFromApi(data) as T; case 'EditHistory': @@ -1729,8 +1892,12 @@ export function convertFromApi(data: any, modelType: string): T { return convertEducationFromApi(data) as T; case 'Employer': return convertEmployerFromApi(data) as T; + case 'EmployerResponse': + return convertEmployerResponseFromApi(data) as T; case 'Guest': return convertGuestFromApi(data) as T; + case 'GuestSessionResponse': + return convertGuestSessionResponseFromApi(data) as T; case 'InterviewFeedback': return convertInterviewFeedbackFromApi(data) as T; case 'InterviewSchedule': @@ -1741,8 +1908,12 @@ export function convertFromApi(data: any, modelType: string): T { return convertJobApplicationFromApi(data) as T; case 'JobFull': return convertJobFullFromApi(data) as T; + case 'JobListResponse': + return convertJobListResponseFromApi(data) as T; case 'JobRequirementsMessage': return convertJobRequirementsMessageFromApi(data) as T; + case 'JobResponse': + return convertJobResponseFromApi(data) as T; case 'MessageReaction': return convertMessageReactionFromApi(data) as T; case 'RAGConfiguration': diff --git a/src/backend/generate_types.py b/src/backend/generate_types.py index 2de1543..991e057 100644 --- a/src/backend/generate_types.py +++ b/src/backend/generate_types.py @@ -2,14 +2,15 @@ """ Enhanced Type Generator - Generate TypeScript types from Pydantic models Now with command line parameters, pre-test validation, TypeScript compilation, -automatic date field conversion functions, and proper enum default handling +automatic date field conversion functions, proper enum default handling, +and NESTED MODEL CONVERSION SUPPORT """ import sys import os import argparse import subprocess -from typing import Any, Dict, List, Optional, Union, get_origin, get_args +from typing import Any, Dict, List, Optional, Union, get_origin, get_args, Set from datetime import datetime from enum import Enum from pathlib import Path @@ -64,7 +65,7 @@ current_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, current_dir) try: - from pydantic import BaseModel + from pydantic import BaseModel # type: ignore except ImportError as e: print(f"Error importing pydantic: {e}") print("Make sure pydantic is installed: pip install pydantic") @@ -138,6 +139,61 @@ def is_date_type(python_type: Any) -> bool: return False +def is_pydantic_model_type(python_type: Any) -> bool: + """Check if a Python type is a Pydantic model""" + # Unwrap any annotations first + python_type = unwrap_annotated_type(python_type) + + # Handle Union types (like Optional[SomeModel]) + origin = get_origin(python_type) + if origin is Union: + args = get_args(python_type) + # Check if any of the union args is a Pydantic model (excluding None) + return any(is_pydantic_model_type(arg) for arg in args if arg is not type(None)) + + # Handle List types (like List[SomeModel]) + if origin is list or origin is List: + args = get_args(python_type) + if args: + return is_pydantic_model_type(args[0]) + + # Check if it's a Pydantic model + try: + if isinstance(python_type, type) and issubclass(python_type, BaseModel): + return python_type != BaseModel + except: + pass + + return False + +def get_model_name_from_type(python_type: Any) -> Optional[str]: + """Extract the model name from a type""" + # Unwrap any annotations first + python_type = unwrap_annotated_type(python_type) + + # Handle Union types + origin = get_origin(python_type) + if origin is Union: + args = get_args(python_type) + # Find the first Pydantic model in the union + for arg in args: + if arg is not type(None): + model_name = get_model_name_from_type(arg) + if model_name: + return model_name + + # Handle List types + if origin is list or origin is List: + args = get_args(python_type) + if args: + return get_model_name_from_type(args[0]) + + # Direct model check + if isinstance(python_type, type) and issubclass(python_type, BaseModel): + return python_type.__name__ + + return None + def get_default_enum_value(field_info: Any, debug: bool = False) -> Optional[Any]: """Extract the specific enum value from a field's default, if it exists""" if not hasattr(field_info, 'default'): @@ -416,11 +472,12 @@ def is_field_optional(field_info: Any, field_type: Any, debug: bool = False) -> print(f" └─ RESULT: Required (fallback - no Optional type, no default)") return False -def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]: +def process_pydantic_model(model_class, all_models: Set[str], debug: bool = False) -> Dict[str, Any]: """Process a Pydantic model and return TypeScript interface definition""" interface_name = model_class.__name__ properties = [] date_fields = [] # Track date fields for conversion functions + model_fields = [] # Track fields that are Pydantic models if debug: print(f" šŸ” Processing model: {interface_name}") @@ -462,6 +519,21 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]: elif debug and ('date' in str(field_type).lower() or 'time' in str(field_type).lower()): print(f" āš ļø Field {ts_name} contains 'date'/'time' but not detected as date type: {field_type}") + # Check if this is a Pydantic model field + if is_pydantic_model_type(field_type): + model_name = get_model_name_from_type(field_type) + if model_name and model_name in all_models: + is_optional = is_field_optional(field_info, field_type, debug) + is_list = get_origin(unwrap_annotated_type(field_type)) in (list, List) + model_fields.append({ + 'name': ts_name, + 'model': model_name, + 'optional': is_optional, + 'is_list': is_list + }) + if debug: + print(f" šŸ”— Model field detected: {ts_name} -> {model_name} (optional: {is_optional}, list: {is_list})") + # Pass field_info to the type converter for default enum handling ts_type = python_type_to_typescript(field_type, field_info, debug) @@ -514,6 +586,21 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]: elif debug and ('date' in str(field_type).lower() or 'time' in str(field_type).lower()): print(f" āš ļø Field {ts_name} contains 'date'/'time' but not detected as date type: {field_type}") + # Check if this is a Pydantic model field + if is_pydantic_model_type(field_type): + model_name = get_model_name_from_type(field_type) + if model_name and model_name in all_models: + is_optional = is_field_optional(field_info, field_type) + is_list = get_origin(unwrap_annotated_type(field_type)) in (list, List) + model_fields.append({ + 'name': ts_name, + 'model': model_name, + 'optional': is_optional, + 'is_list': is_list + }) + if debug: + print(f" šŸ”— Model field detected: {ts_name} -> {model_name} (optional: {is_optional}, list: {is_list})") + # Pass field_info to the type converter for default enum handling ts_type = python_type_to_typescript(field_type, field_info, debug) @@ -535,7 +622,8 @@ def process_pydantic_model(model_class, debug: bool = False) -> Dict[str, Any]: return { 'name': interface_name, 'properties': properties, - 'date_fields': date_fields + 'date_fields': date_fields, + 'model_fields': model_fields } def process_enum(enum_class) -> Dict[str, Any]: @@ -549,38 +637,87 @@ def process_enum(enum_class) -> Dict[str, Any]: 'values': " | ".join(values) } +def build_conversion_dependency_graph(interfaces: List[Dict[str, Any]]) -> Dict[str, Set[str]]: + """Build a graph of which models depend on which other models for conversion""" + dependencies = {} + + # First pass: identify which models have conversions + models_with_conversions = set() + for interface in interfaces: + if interface.get('date_fields') or interface.get('model_fields'): + models_with_conversions.add(interface['name']) + + # Second pass: build dependency graph + for interface in interfaces: + interface_name = interface['name'] + deps = set() + + # Add dependencies for nested models + for model_field in interface.get('model_fields', []): + model_name = model_field['model'] + if model_name in models_with_conversions: + deps.add(model_name) + + if deps or interface.get('date_fields'): + dependencies[interface_name] = deps + + return dependencies + def generate_conversion_functions(interfaces: List[Dict[str, Any]]) -> str: - """Generate TypeScript conversion functions for models with date fields""" + """Generate TypeScript conversion functions for models with date fields or nested models""" + # Build dependency graph + dependencies = build_conversion_dependency_graph(interfaces) + + if not dependencies: + return "" + conversion_functions = [] for interface in interfaces: interface_name = interface['name'] date_fields = interface.get('date_fields', []) + model_fields = interface.get('model_fields', []) - if not date_fields: - continue # Skip interfaces without date fields + # Skip if no conversion needed + if not date_fields and not model_fields: + continue + + # Check if any model fields need conversion + model_fields_needing_conversion = [ + mf for mf in model_fields + if mf['model'] in dependencies + ] + + if not date_fields and not model_fields_needing_conversion: + continue function_name = f"convert{interface_name}FromApi" # Generate function func_lines = [ f"/**", - f" * Convert {interface_name} from API response, parsing date fields", - f" * Date fields: {', '.join([f['name'] for f in date_fields])}", + f" * Convert {interface_name} from API response", + ] + + if date_fields: + func_lines.append(f" * Date fields: {', '.join([f['name'] for f in date_fields])}") + if model_fields_needing_conversion: + func_lines.append(f" * Nested models: {', '.join([f'{mf['name']} ({mf['model']})' for mf in model_fields_needing_conversion])}") + + func_lines.extend([ f" */", f"export function {function_name}(data: any): {interface_name} {{", f" if (!data) return data;", f" ", f" return {{", f" ...data," - ] + ]) - # Add date field conversions with validation + # Add date field conversions for date_field in date_fields: field_name = date_field['name'] is_optional = date_field['optional'] - # Add a comment for clarity func_lines.append(f" // Convert {field_name} from ISO string to Date") if is_optional: @@ -588,6 +725,26 @@ def generate_conversion_functions(interfaces: List[Dict[str, Any]]) -> str: else: func_lines.append(f" {field_name}: new Date(data.{field_name}),") + # Add nested model conversions + for model_field in model_fields_needing_conversion: + field_name = model_field['name'] + model_name = model_field['model'] + is_optional = model_field['optional'] + is_list = model_field['is_list'] + + func_lines.append(f" // Convert nested {model_name} model") + + if is_list: + if is_optional: + func_lines.append(f" {field_name}: data.{field_name} ? data.{field_name}.map((item: any) => convert{model_name}FromApi(item)) : undefined,") + else: + func_lines.append(f" {field_name}: data.{field_name}.map((item: any) => convert{model_name}FromApi(item)),") + else: + if is_optional: + func_lines.append(f" {field_name}: data.{field_name} ? convert{model_name}FromApi(data.{field_name}) : undefined,") + else: + func_lines.append(f" {field_name}: convert{model_name}FromApi(data.{field_name}),") + func_lines.extend([ f" }};", f"}}" @@ -601,11 +758,11 @@ def generate_conversion_functions(interfaces: List[Dict[str, Any]]) -> str: # Generate the conversion functions section result = [ "// ============================", - "// Date Conversion Functions", + "// Date and Nested Model Conversion Functions", "// ============================", "", "// These functions convert API responses to properly typed objects", - "// with Date objects instead of ISO date strings", + "// with Date objects instead of ISO date strings and nested model conversions", "", ] @@ -613,9 +770,9 @@ def generate_conversion_functions(interfaces: List[Dict[str, Any]]) -> str: result.append("") # Generate a generic converter function - models_with_dates = [interface['name'] for interface in interfaces if interface.get('date_fields')] + models_with_conversions = list(dependencies.keys()) - if models_with_dates: + if models_with_conversions: result.extend([ "/**", " * Generic converter that automatically selects the right conversion function", @@ -627,7 +784,7 @@ def generate_conversion_functions(interfaces: List[Dict[str, Any]]) -> str: " switch (modelType) {" ]) - for model_name in models_with_dates: + for model_name in sorted(models_with_conversions): result.append(f" case '{model_name}':") result.append(f" return convert{model_name}FromApi(data) as T;") @@ -671,8 +828,17 @@ def generate_typescript_interfaces(source_file: str, debug: bool = False): interfaces = [] enums = [] + all_models = set() - # Scan the models module + # First pass: collect all model names + for name in dir(models_module): + obj = getattr(models_module, name) + if (isinstance(obj, type) and + issubclass(obj, BaseModel) and + obj != BaseModel): + all_models.add(name) + + # Second pass: process models with knowledge of all models for name in dir(models_module): obj = getattr(models_module, name) @@ -686,10 +852,17 @@ def generate_typescript_interfaces(source_file: str, debug: bool = False): issubclass(obj, BaseModel) and obj != BaseModel): - interface = process_pydantic_model(obj, debug) + interface = process_pydantic_model(obj, all_models, debug) interfaces.append(interface) date_count = len(interface.get('date_fields', [])) - print(f" āœ… Found Pydantic model: {name}" + (f" ({date_count} date fields)" if date_count > 0 else "")) + nested_count = len(interface.get('model_fields', [])) + status_parts = [] + if date_count > 0: + status_parts.append(f"{date_count} date fields") + if nested_count > 0: + status_parts.append(f"{nested_count} nested models") + status = f" ({', '.join(status_parts)})" if status_parts else "" + print(f" āœ… Found Pydantic model: {name}{status}") # Check if it's an Enum elif (isinstance(obj, type) and @@ -707,8 +880,10 @@ def generate_typescript_interfaces(source_file: str, debug: bool = False): continue total_date_fields = sum(len(interface.get('date_fields', [])) for interface in interfaces) + total_nested_models = sum(len(interface.get('model_fields', [])) for interface in interfaces) print(f"\nšŸ“Š Found {len(interfaces)} interfaces and {len(enums)} enums") print(f"šŸ—“ļø Found {total_date_fields} date fields across all models") + print(f"šŸ”— Found {total_nested_models} nested model fields requiring conversion") # Generate TypeScript content ts_content = f"""// Generated TypeScript types from Pydantic models @@ -781,7 +956,7 @@ def compile_typescript(ts_file: str) -> bool: def main(): """Main function with command line argument parsing""" parser = argparse.ArgumentParser( - description='Generate TypeScript types from Pydantic models with date conversion functions and proper enum handling', + description='Generate TypeScript types from Pydantic models with nested model conversion support', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: @@ -792,12 +967,19 @@ Examples: python generate_types.py --debug # Enable debug output python generate_types.py --source models.py --output types.ts --skip-test --skip-compile --debug -Generated conversion functions can be used like: - const candidate = convertCandidateFromApi(apiResponse); - const jobs = convertArrayFromApi(apiResponse, 'Job'); +Generated conversion functions now support nested models: + // If JobRequirementsMessage has a 'job' field of type Job + const message = convertJobRequirementsMessageFromApi(apiResponse); + // The nested job field will also be converted automatically -Enum types are now properly handled: - status: ApiStatusType = ApiStatusType.DONE -> status: ApiStatusType (not locked to "done") + // Arrays of models are also supported + const messages = convertArrayFromApi(apiResponse, 'JobRequirementsMessage'); + +The conversion functions handle: + - Date field conversion (ISO strings to Date objects) + - Nested model conversion (recursive conversion of Pydantic model fields) + - Arrays of models + - Optional fields """ ) @@ -834,12 +1016,12 @@ Enum types are now properly handled: parser.add_argument( '--version', '-v', action='version', - version='TypeScript Generator 3.2 (Fixed Enum Default Handling)' + version='TypeScript Generator 4.0 (With Nested Model Conversion Support)' ) args = parser.parse_args() - print("šŸš€ Enhanced TypeScript Type Generator with Fixed Enum Handling") + print("šŸš€ Enhanced TypeScript Type Generator with Nested Model Conversion") print("=" * 60) print(f"šŸ“ Source file: {args.source}") print(f"šŸ“ Output file: {args.output}") @@ -885,29 +1067,24 @@ Enum types are now properly handled: # Count conversion functions and provide detailed feedback conversion_count = ts_content.count('export function convert') - ts_content.count('convertFromApi') - ts_content.count('convertArrayFromApi') enum_type_count = ts_content.count('export type') + nested_conversion_count = ts_content.count('// Convert nested') if conversion_count > 0: - print(f"šŸ—“ļø Generated {conversion_count} date conversion functions") + print(f"šŸ—“ļø Generated {conversion_count} conversion functions") + if nested_conversion_count > 0: + print(f"šŸ”— Including {nested_conversion_count} nested model conversions") if enum_type_count > 0: - print(f"šŸŽÆ Generated {enum_type_count} enum types (properly allowing all values)") + print(f"šŸŽÆ Generated {enum_type_count} enum types") if args.debug: - # Show which models have date conversion - models_with_dates = [] + # Show which models have conversions + models_with_conversions = [] for line in ts_content.split('\n'): if line.startswith('export function convert') and 'FromApi' in line and 'convertFromApi' not in line: model_name = line.split('convert')[1].split('FromApi')[0] - models_with_dates.append(model_name) - if models_with_dates: - print(f" Models with date conversion: {', '.join(models_with_dates)}") - - # Provide troubleshooting info if debug mode - if args.debug: - print(f"\nšŸ› Debug mode was enabled. If you see incorrect type conversions:") - print(f" 1. Check the debug output above for enum default handling") - print(f" 2. Look for 'šŸ“… Date type check' lines for date handling") - print(f" 3. Look for 'āš ļø' warnings about fallback types") - print(f" 4. Verify your Pydantic model field types and defaults are correct") + models_with_conversions.append(model_name) + if models_with_conversions: + print(f" Models with conversions: {', '.join(models_with_conversions)}") # Step 5: Compile TypeScript (unless skipped) if not args.skip_compile: @@ -924,19 +1101,24 @@ Enum types are now properly handled: print(f"āœ… Generated {args.output} from {args.source}") print(f"āœ… File size: {file_size} characters") if conversion_count > 0: - print(f"āœ… Date conversion functions: {conversion_count}") + print(f"āœ… Conversion functions: {conversion_count}") + if nested_conversion_count > 0: + print(f"āœ… Nested model conversions: {nested_conversion_count}") if enum_type_count > 0: - print(f"āœ… Enum types (with full value range): {enum_type_count}") + print(f"āœ… Enum types: {enum_type_count}") if not args.skip_test: print("āœ… Model validation passed") if not args.skip_compile: print("āœ… TypeScript syntax validated") print(f"\nšŸ’” Usage in your TypeScript project:") - print(f" import {{ Candidate, Employer, Job, convertCandidateFromApi }} from './{Path(args.output).stem}';") + print(f" import {{ Candidate, Job, convertJobRequirementsMessageFromApi }} from './{Path(args.output).stem}';") if conversion_count > 0: - print(f" const candidate = convertCandidateFromApi(apiResponse);") - print(f" const jobs = convertArrayFromApi(apiResponse, 'Job');") + print(f"\n // Example with nested model conversion:") + print(f" const message = convertJobRequirementsMessageFromApi(apiResponse);") + print(f" // The nested 'job' field is automatically converted too!") + print(f"\n // For arrays:") + print(f" const messages = convertArrayFromApi(apiResponse, 'JobRequirementsMessage');") return True