minor fixes
[sgn.git] / lib / CXGN / Pedigree / AddCrosses.pm
blob77d52119f1a232db774541836e6a90b1376b90ab
1 package CXGN::Pedigree::AddCrosses;
3 =head1 NAME
5 CXGN::Pedigree::AddCrosses - a module to add cross experiments.
7 =head1 USAGE
9 my $cross_add = CXGN::Pedigree::AddCrosses->new({ schema => $schema, location => $location_name, program => $program_name, crosses => \@array_of_pedigree_objects} );
10 my $validated = $cross_add->validate_crosses(); #is true when all of the crosses are valid and the accessions they point to exist in the database.
11 $cross_add->add_crosses();
13 =head1 DESCRIPTION
15 Adds an array of crosses. The stock names used in the cross must already exist in the database, and the verify function does this check. This module is intended to be used in independent loading scripts and interactive dialogs.
17 =head1 AUTHORS
19 Jeremy D. Edwards (jde22@cornell.edu)
21 =cut
23 use Moose;
24 use MooseX::FollowPBP;
25 use Moose::Util::TypeConstraints;
26 use Try::Tiny;
27 use Bio::GeneticRelationships::Pedigree;
28 use Bio::GeneticRelationships::Individual;
29 use CXGN::Stock::StockLookup;
30 use CXGN::Location::LocationLookup;
31 use CXGN::BreedersToolbox::Projects;
32 use CXGN::Trial;
33 use CXGN::Trial::Folder;
34 use SGN::Model::Cvterm;
36 class_type 'Pedigree', { class => 'Bio::GeneticRelationships::Pedigree' };
37 has 'chado_schema' => (
38 is => 'rw',
39 isa => 'DBIx::Class::Schema',
40 predicate => 'has_chado_schema',
41 required => 1,
43 has 'phenome_schema' => (
44 is => 'rw',
45 isa => 'DBIx::Class::Schema',
46 predicate => 'has_phenome_schema',
47 required => 1,
49 has 'metadata_schema' => (
50 is => 'rw',
51 isa => 'DBIx::Class::Schema',
52 predicate => 'has_metadata_schema',
53 required => 0,
55 has 'dbh' => (is => 'rw',predicate => 'has_dbh', required => 1,);
56 has 'crosses' => (isa =>'ArrayRef[Pedigree]', is => 'rw', predicate => 'has_crosses', required => 1,);
57 has 'location' => (isa =>'Str', is => 'rw', predicate => 'has_location', required => 1,);
58 has 'program' => (isa =>'Str', is => 'rw', predicate => 'has_program', required => 1,);
59 has 'owner_name' => (isa => 'Str', is => 'rw', predicate => 'has_owner_name', required => 1,);
60 has 'parent_folder_id' => (isa => 'Str', is => 'rw', predicate => 'has_parent_folder_id', required => 0,);
62 sub add_crosses {
63 my $self = shift;
64 my $chado_schema = $self->get_chado_schema();
65 my $phenome_schema = $self->get_phenome_schema();
66 my @crosses;
67 my $location_lookup;
68 my $geolocation;
69 my $program;
70 my $program_lookup;
71 my $transaction_error;
72 my @added_stock_ids;
73 my $parent_folder_id;
75 #lookup user by name
76 my $owner_name = $self->get_owner_name();
77 $parent_folder_id = $self->get_parent_folder_id() || 0;
78 my $dbh = $self->get_dbh();
79 my $owner_sp_person_id = CXGN::People::Person->get_person_by_username($dbh, $owner_name); #add person id as an option.
81 if (!$self->validate_crosses()) {
82 print STDERR "Invalid pedigrees in array. No crosses will be added\n";
83 return;
86 #add all crosses in a single transaction
87 my $coderef = sub {
89 #get cvterms for parents and offspring
90 my $female_parent_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'female_parent', 'stock_relationship');
92 my $male_parent_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'male_parent', 'stock_relationship');
93 my $progeny_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'offspring_of', 'stock_relationship');
95 #get cvterm for cross_name or create if not found
96 my $cross_name_cvterm = $chado_schema->resultset("Cv::Cvterm")
97 ->find({
98 name => 'cross_name',
99 });
100 if (!$cross_name_cvterm) {
101 $cross_name_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'cross_name', 'nd_experiment_property');
103 #get cvterm for cross_type or create if not found
104 my $cross_type_cvterm = $chado_schema->resultset("Cv::Cvterm")
105 ->find({
106 name => 'cross_type',
109 if (!$cross_type_cvterm) {
110 $cross_type_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'cross_type', 'nd_experiment_property');
113 #get cvterm for cross_experiment
114 my $cross_experiment_type_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'cross_experiment', 'experiment_type');
116 #get cvterm for stock type cross
117 my $cross_stock_type_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'cross', 'stock_type');
119 print STDERR "\n\ncvterm from addcrosses: ".$cross_stock_type_cvterm->cvterm_id()."\n\n";
121 #lookup location by name
122 $location_lookup = CXGN::Location::LocationLookup->new({ schema => $chado_schema, location_name => $self->get_location });
123 $geolocation = $location_lookup->get_geolocation();
125 #lookup program by name
126 $program_lookup = CXGN::BreedersToolbox::Projects->new({ schema => $chado_schema});
127 $program = $program_lookup->get_breeding_program_by_name($self->get_program());
129 @crosses = @{$self->get_crosses()};
131 foreach my $pedigree (@crosses) {
132 my $experiment;
133 my $cross_stock;
134 my $organism_id;
135 my $female_parent_name;
136 my $male_parent_name;
137 my $female_parent;
138 my $male_parent;
139 my $population_stock;
140 my $project;
141 my $cross_type = $pedigree->get_cross_type();
142 my $cross_name = $pedigree->get_name();
144 if ($pedigree->has_female_parent()) {
145 $female_parent_name = $pedigree->get_female_parent()->get_name();
146 $female_parent = $self->_get_accession($female_parent_name);
149 if ($pedigree->has_male_parent()) {
150 $male_parent_name = $pedigree->get_male_parent()->get_name();
151 $male_parent = $self->_get_accession($male_parent_name);
154 #organism of cross experiment will be the same as the female parent
155 if ($female_parent) {
156 $organism_id = $female_parent->organism_id();
157 } else {
158 $organism_id = $male_parent->organism_id();
161 #create cross project
162 $project = $chado_schema->resultset('Project::Project')
163 ->create({
164 name => $cross_name,
165 description => $cross_name,
168 #add error if cross name exists
170 #add cross to folder if one was specified
171 if ($parent_folder_id) {
172 my $folder = CXGN::Trial::Folder->new(
174 bcs_schema => $chado_schema,
175 folder_id => $project->project_id(),
178 $folder->associate_parent($parent_folder_id);
181 #set projectprop so that projects corresponding to crosses can be identified
182 my $prop_row = $chado_schema->resultset("Project::Projectprop")
183 ->create({
184 type_id => $cross_stock_type_cvterm->cvterm_id,
185 project_id => $project->project_id(),
187 $prop_row->insert();
189 #create cross experiment
190 $experiment = $chado_schema->resultset('NaturalDiversity::NdExperiment')->create(
192 nd_geolocation_id => $geolocation->nd_geolocation_id(),
193 type_id => $cross_experiment_type_cvterm->cvterm_id(),
194 } );
196 #store the cross name as an experiment prop
197 $experiment->find_or_create_related('nd_experimentprops' , {
198 nd_experiment_id => $experiment->nd_experiment_id(),
199 type_id => $cross_name_cvterm->cvterm_id(),
200 value => $cross_name,
203 #store the cross type as an experiment prop
204 $experiment->find_or_create_related('nd_experimentprops' , {
205 nd_experiment_id => $experiment->nd_experiment_id(),
206 type_id => $cross_type_cvterm->cvterm_id(),
207 value => $cross_type,
210 #link the parents to the experiment
211 if ($female_parent) {
212 $experiment->find_or_create_related('nd_experiment_stocks' , {
213 stock_id => $female_parent->stock_id(),
214 type_id => $female_parent_cvterm->cvterm_id(),
217 if ($male_parent) {
218 $experiment->find_or_create_related('nd_experiment_stocks' , {
219 stock_id => $male_parent->stock_id(),
220 type_id => $male_parent_cvterm->cvterm_id(),
223 if ($cross_type eq "self" && $female_parent) {
224 $experiment->find_or_create_related('nd_experiment_stocks' , {
225 stock_id => $female_parent->stock_id(),
226 type_id => $male_parent_cvterm->cvterm_id(),
230 #create a stock of type cross
231 $cross_stock = $chado_schema->resultset("Stock::Stock")->find_or_create(
232 { organism_id => $organism_id,
233 name => $cross_name,
234 uniquename => $cross_name,
235 type_id => $cross_stock_type_cvterm->cvterm_id,
236 } );
238 #add stock_id of cross to an array so that the owner can be associated in the phenome schema after the transaction on the chado schema completes
239 push (@added_stock_ids, $cross_stock->stock_id());
242 #link parents to the stock of type cross
243 if ($female_parent) {
244 $cross_stock
245 ->find_or_create_related('stock_relationship_objects', {
246 type_id => $female_parent_cvterm->cvterm_id(),
247 object_id => $cross_stock->stock_id(),
248 subject_id => $female_parent->stock_id(),
249 value => $cross_type,
250 } );
253 if ($male_parent) {
254 $cross_stock
255 ->find_or_create_related('stock_relationship_objects', {
256 type_id => $male_parent_cvterm->cvterm_id(),
257 object_id => $cross_stock->stock_id(),
258 subject_id => $male_parent->stock_id(),
259 } );
262 if ($cross_type eq "self" && $female_parent) {
263 $cross_stock
264 ->find_or_create_related('stock_relationship_objects', {
265 type_id => $male_parent_cvterm->cvterm_id(),
266 object_id => $cross_stock->stock_id(),
267 subject_id => $female_parent->stock_id(),
268 } );
272 #link the stock of type cross to the experiment
273 $experiment->find_or_create_related('nd_experiment_stocks' , {
274 stock_id => $cross_stock->stock_id(),
275 type_id => $progeny_cvterm->cvterm_id(),
277 #link the experiment to the project
278 $experiment->find_or_create_related('nd_experiment_projects', {
279 project_id => $project->project_id()
280 } );
282 #link the cross program to the breeding program
283 my $trial_object = CXGN::Trial->new({ bcs_schema => $chado_schema, trial_id => $project->project_id() });
284 $trial_object->set_breeding_program($program->project_id());
286 #add the cross type to the experiment as an experimentprop
287 $experiment
288 ->find_or_create_related('nd_experimentprops' , {
289 nd_experiment_id => $experiment->nd_experiment_id(),
290 type_id => $cross_type_cvterm->cvterm_id(),
291 value => $cross_type,
298 #try to add all crosses in a transaction
299 try {
300 $chado_schema->txn_do($coderef);
301 } catch {
302 $transaction_error = $_;
305 if ($transaction_error) {
306 print STDERR "Transaction error creating a cross: $transaction_error\n";
307 return;
310 foreach my $stock_id (@added_stock_ids) {
311 #add the owner for this stock
312 $phenome_schema->resultset("StockOwner")
313 ->find_or_create({
314 stock_id => $stock_id,
315 sp_person_id => $owner_sp_person_id,
319 return 1;
323 sub validate_crosses {
324 my $self = shift;
325 my $chado_schema = $self->get_chado_schema();
326 my @crosses = @{$self->get_crosses()};
327 my $invalid_cross_count = 0;
328 my $program;
329 my $location_lookup;
330 my $trial_lookup;
331 my $program_lookup;
332 my $geolocation;
334 $location_lookup = CXGN::Location::LocationLookup->new({ schema => $chado_schema, location_name => $self->get_location() });
335 $geolocation = $location_lookup->get_geolocation();
337 if (!$geolocation) {
338 print STDERR "Location ".$self->get_location()." not found\n";
339 return;
342 $program_lookup = CXGN::BreedersToolbox::Projects->new({ schema => $chado_schema});
343 $program = $program_lookup->get_breeding_program_by_name($self->get_program());
344 if (!$program) {
345 print STDERR "Breeding program ". $self->get_program() ." not found\n";
346 return;
349 foreach my $cross (@crosses) {
350 my $validated_cross = $self->_validate_cross($cross);
352 if (!$validated_cross) {
353 $invalid_cross_count++;
358 if ($invalid_cross_count > 0) {
359 print STDERR "There were $invalid_cross_count invalid crosses\n";
360 return;
363 return 1;
366 sub _validate_cross {
367 my $self = shift;
368 my $pedigree = shift;
369 my $chado_schema = $self->get_chado_schema();
370 my $name = $pedigree->get_name();
371 my $cross_type = $pedigree->get_cross_type();
372 my $female_parent_name;
373 my $male_parent_name;
374 my $female_parent;
375 my $male_parent;
377 if ($cross_type eq "biparental") {
378 $female_parent_name = $pedigree->get_female_parent()->get_name();
379 $male_parent_name = $pedigree->get_male_parent()->get_name();
380 $female_parent = $self->_get_accession($female_parent_name);
381 $male_parent = $self->_get_accession($male_parent_name);
383 if (!$female_parent || !$male_parent) {
384 print STDERR "Parent $female_parent_name or $male_parent_name in pedigree is not a stock\n";
385 return;
388 } elsif ($cross_type eq "self") {
389 $female_parent_name = $pedigree->get_female_parent()->get_name();
390 $female_parent = $self->_get_accession($female_parent_name);
392 if (!$female_parent) {
393 print STDERR "Parent $female_parent_name in pedigree is not a stock\n";
394 return;
397 } elsif ($cross_type eq "open") {
398 $female_parent_name = $pedigree->get_female_parent()->get_name();
399 $female_parent = $self->_get_accession($female_parent_name);
401 if (!$female_parent) {
402 print STDERR "Parent $female_parent_name in pedigree is not a stock\n";
403 return;
406 #add support for other cross types here
408 #else {
409 # return;
412 return 1;
415 sub _get_accession {
416 my $self = shift;
417 my $accession_name = shift;
418 my $chado_schema = $self->get_chado_schema();
419 my $stock_lookup = CXGN::Stock::StockLookup->new(schema => $chado_schema);
420 my $stock;
421 my $accession_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'accession', 'stock_type');
422 my $population_cvterm = SGN::Model::Cvterm->get_cvterm_row($chado_schema, 'population', 'stock_type');
424 $stock_lookup->set_stock_name($accession_name);
425 $stock = $stock_lookup->get_stock_exact();
427 if (!$stock) {
428 print STDERR "Name in pedigree is not a stock\n";
429 return;
432 if (($stock->type_id() != $accession_cvterm->cvterm_id()) && ($stock->type_id() != $population_cvterm->cvterm_id()) ) {
433 print STDERR "Name in pedigree is not a stock of type accession or population\n";
434 return;
437 return $stock;
440 #######
442 #######