
    P1in                    N   d dl mZ d dlZd dlZd dlmZmZmZ d dlmZm	Z	m
Z
 d dlmZ d dlmZ d dlmZ d dlmZ d d	lmZ d d
lmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlm Z  d dl!m"Z" d dl#m$Z$m%Z%m&Z&m'Z' d dl(m)Z) d dl*m+Z, ddl-m.Z. dZ/ddZ0ddZ1ddZ2d dZ3 G d de      Z4y)!    )annotationsN)datetime	timedeltatimezone)AnyDictList)cache)settings)	send_mail)r   )transactionget_user_model)GroupRelation)APIView)Response)IsAuthenticated)SessionAuthentication)JWTAuthentication)extend_schemaOpenApiParameterOpenApiTypesinline_serializer)serializers)Person   )CustomPageNumberPaginationu  
Return 3-party group relations (三合/三刑) for the logged-in user.

This endpoint manages background processing of complex BaZi relationship calculations
between the logged-in user (owner) and their associated persons.

## Processing States

### "processing" Response
Background task is running or about to start. Returns:
```json
{
  "status": "processing",
  "results": [],
  "count": 0
}
```

### "ready" Response  
Results are available. Returns paginated data with full BaZi person data:
```json
{
  "status": "ready", 
  "results": [
    {
      "t": 0,
      "p1": 123,
      "p2": 456, 
      "by": [
        ["o", "d", "e", 2],
        ["p1", "d", "e", 6], 
        ["p2", "d", "e", 10]
      ],
      "p1_data": {
        "name": "Person 1",
        "gender": "M",
        "birth_date": "1990-01-01",
        "birth_time": "12:00:00",
        "bazi": { "yg": 0, "ye": 2, "mg": 1, "me": 5, "dg": 1, "de": 7, "hg": 5, "he": 9 }
      },
      "p2_data": {
        "name": "Person 2", 
        "gender": "F",
        "birth_date": "1985-05-15",
        "birth_time": "08:30:00",
        "bazi": { "yg": 3, "ye": 1, "mg": 4, "me": 8, "dg": 2, "de": 4, "hg": 6, "he": 0 }
      }
    }
  ],
  "count": 1,
  "next": "http://example.com/api/bazi/person-relations/?page=2",
  "previous": null
}
```

### "error" Response
Previous run exceeded timeout. Returns:
```json
{
  "status": "error",
  "retry_after_seconds": 300,
  "results": [],
  "count": 0
}
```

## Data Structure Details

### Relation Types (`t` field)
- `0`: 三合 (sanhe) - Three Harmony relationship (positive)
- `1`: 三刑 (sanxing) - Three Punishment relationship (negative)

### Person IDs (`p1`, `p2` fields)
- Integer IDs referring to BaZi Person records
- Both persons are associated with the logged-in user
- Relationship involves: Owner + Person1 + Person2

### Formation Details (`by` field)
Array of 3 elements showing how the relationship is formed:
```
[
  ["o", "d", "e", <earth_branch_index>],   // Owner's day pillar earth branch
  ["p1", "d", "e", <earth_branch_index>],  // Person1's day pillar earth branch  
  ["p2", "d", "e", <earth_branch_index>]   // Person2's day pillar earth branch
]
```

**Earth Branch Indices:**
- 0: 子 (zi), 1: 丑 (chou), 2: 寅 (yin), 3: 卯 (mao)
- 4: 辰 (chen), 5: 巳 (si), 6: 午 (wu), 7: 未 (wei)
- 8: 申 (shen), 9: 酉 (you), 10: 戌 (xu), 11: 亥 (hai)

## Example Relationships

### 三合 (Three Harmony) Example
```json
{
  "t": 0,
  "p1": 123,
  "p2": 456,
  "by": [
    ["o", "d", "e", 2],   // Owner: 寅 (yin)
    ["p1", "d", "e", 6],  // Person 123: 午 (wu)  
    ["p2", "d", "e", 10]  // Person 456: 戌 (xu)
  ]
}
```
This represents 寅午戌三合 (yin-wu-xu three harmony forming fire triad).

### 三刑 (Three Punishment) Example
```json
{
  "t": 1, 
  "p1": 789,
  "p2": 101,
  "by": [
    ["o", "d", "e", 2],   // Owner: 寅 (yin)
    ["p1", "d", "e", 5],  // Person 789: 巳 (si)
    ["p2", "d", "e", 8]   // Person 101: 申 (shen)  
  ]
}
```
This represents 寅巳申三刑 (yin-si-shen three punishments).

## Background Processing

- **Automatic Trigger**: Processing starts automatically on first request if state is 'idle'
- **Recalculation**: Triggered when owner's DOB/time changes or persons are added/deleted
- **Timeout**: Dynamic timeout based on number of records (10s per 100 records, minimum 10s)
- **Error Recovery**: Automatic retry after 5 minutes for timed-out operations
- **Admin Notification**: Email sent to TECH_CONTACT on timeout (if configured)

## Pagination

Results are paginated with standard DRF pagination:
- `count`: Total number of group relations
- `next`: URL for next page (if available)
- `previous`: URL for previous page (if available)
- `results`: Array of relation objects for current page

Default page size follows `PAGINATE_BY['default']` setting.

## Complete Examples

**Processing State Response:**
```json
{
  "status": "processing",
  "count": 0,
  "results": []
}
```

**Ready State with Results:**
```json
{
  "status": "ready",
  "count": 2,
  "next": null,
  "previous": null,
  "results": [
    {
      "t": 0,
      "p1": 123,
      "p2": 456,
      "by": [
        ["o", "d", "e", 2],
        ["p1", "d", "e", 6], 
        ["p2", "d", "e", 10]
      ],
      "p1_data": {
        "name": "Person 1",
        "gender": "M",
        "birth_date": "1990-01-01",
        "birth_time": "12:00:00",
        "bazi": { "yg": 0, "ye": 2, "mg": 1, "me": 5, "dg": 1, "de": 7, "hg": 5, "he": 9 }
      },
      "p2_data": {
        "name": "Person 2", 
        "gender": "F",
        "birth_date": "1985-05-15",
        "birth_time": "08:30:00",
        "bazi": { "yg": 3, "ye": 1, "mg": 4, "me": 8, "dg": 2, "de": 4, "hg": 6, "he": 0 }
      }
    },
    {
      "t": 1,
      "p1": 789,
      "p2": 101,
      "by": [
        ["o", "d", "e", 2],
        ["p1", "d", "e", 5],
        ["p2", "d", "e", 8]
      ],
      "p1_data": {
        "name": "Person 3",
        "gender": "M",
        "birth_date": "1985-03-12",
        "birth_time": "14:30:00",
        "bazi": { "yg": 2, "ye": 5, "mg": 3, "me": 2, "dg": 4, "de": 8, "hg": 7, "he": 11 }
      },
      "p2_data": {
        "name": "Person 4",
        "gender": "F",
        "birth_date": "1992-09-20",
        "birth_time": "09:15:00",
        "bazi": { "yg": 8, "ye": 8, "mg": 5, "me": 9, "dg": 9, "de": 3, "hg": 1, "he": 5 }
      }
    }
  ]
}
```

**Error State Response:**
```json
{
  "status": "error",
  "retry_after_seconds": 300,
  "count": 0,
  "results": []
}
```
c                L    d|  d}|dz   |dz   |dz   |dz   |dz   |dz   |d	z   d
S )Nz	bazi:grp::state
started_at
updated_aterror_atlast_retry_atresultscount)r    r!   r"   r#   r$   r%   r&    )user_idprefixs     </home/cursorai/projects/iching/api/views_person_relations.py_cache_keysr+      sR    	#F'!|+|+Z'/1I%'!     c                 F    t        j                         j                         S N)dj_tznow	isoformatr'   r,   r*   _now_isor2   	  s    99;  ""r,   c           
         dd l }dd l}dd l}dd l}dd l} |j
                  d      	 |j                         }j                  dt                j                  d|        |j                  j                  |j                  j                  |d            s|}t        d      D ]s  }|j                  j                  |      }	|	|k(  r n|	}|j                  j                  |j                  j                  |d            s]|}j                  d|         n| j                  d       j                  d	|        j                  d
|j                  j                  |      r|j                  t               d d nd        t#        d| d      |j                  j                  |d      }
|j                  j%                  |
      s!d|
 }j                  |       t#        |      |j&                  j)                         }|j+                  dd      |d<   ||d<   d|d<   j                  d|j+                  d       d|j+                  d              j                  d|j                                 j                  d|        j                  d         j                  d|        j                  d|
        j                  d|j,                          j                  d         	 ddlm ddlm dd l}dd l} fd }|j;                  |d!"      }|j=                           G d# d$      } ||j?                               }j                  d'  d(|jD                   d)       dd l}|jG                  d*       |jI                          j                  d+|jD                   d,       y j                  d+|jD                   d-|jJ                          	 |jM                  d.      \  }}|r8d/  d0|jO                  d1d23       }j                  |       tQ        d4|        |r9d/  d5|jO                  d1d23       }j                  |       tQ        d6|        y y # t@        $ r&}j                  d%  d&tC        |               d }~ww xY w# |jR                  $ rq j                  d7|jD                   d8       |jU                          |jM                         \  }}|r*j                  d/  d9|jO                  d1d23              Y y Y y w xY w# t@        $ rI}j                  d:  d&tC        |              j                  d; |jV                                  d }~ww xY w)<Nr   api_person_relationsz
__file__: z!Current working directory (cwd): z	manage.py   z%Found manage.py in parent directory: zBmanage.py not found in current directory or any parent directorieszCurrent cwd: zFiles in current cwd: 
   zN/Azmanage.py not found in z or any parent directorieszmanage.py not found at 
DJANGO_ENVdevelopment
PYTHONPATHziching.settingsDJANGO_SETTINGS_MODULEzEnvironment: DJANGO_ENV=z, PYTHONPATH=zCurrent working directory: zTarget working directory: z+Spawning background recalculation for user zWorking directory: zmanage.py path: zPython executable: z,Using Django call_command approach for user )call_command)CommandErrorc            	     x   	 ddl m}   |        }	 |j                  j                        }d|_        t        j                         |_        |j                  ddg       j                  d         dd       j                  d        	 |j                          d|_        t        j                         |_        |j                  ddg       y
# |j                  $ r j                  d d	       Y y
w xY w# |j                  $ r Y y
w xY w# $ r}j                  d dt        |              	 j                          d|_        t        j                         |_        |j                  ddg       n# j                  $ r Y nw xY wY d
}~y
Y d
}~y
d
}~wt         $ r}j                  d dt        |              	 j                          d|_        t        j                         |_        |j                  ddg       n# j                  $ r Y nw xY wY d
}~y
Y d
}~y
d
}~ww xY w)zJRun the management command in a separate thread with proper Django contextr   r   id
processinggroup_relations_stategroup_relations_started_atupdate_fieldsUser z
 not foundNz/Starting management command execution for user recalc_bazi_relationsr   )user	verbosityz3Management command completed successfully for user 	completedgroup_relations_updated_atz#Management command failed for user : errorgroup_relations_error_atz0Unexpected error in management command for user )django.contrib.authr   objectsgetrA   r/   r0   rB   saveDoesNotExistrL   inforefresh_from_dbrJ   strrM   	Exception)r   Userusrer<   r;   loggerr(   s       r*   run_management_commandz-_spawn_recalc.<locals>.run_management_commandT  s9   0B)+D"ll..'.:4@19>60GIe/fg
 KK"QRYQZ [\ !!8wRSTKK"UV]U^ _`++-4?19>60GIe/fg!  ,, uWIZ%@A"  ,,  $ 	LL#FwirRUVWRXQY!Z[++-4;17<yy{40GIc/de,,  f ! 	LL#ST[S\\^_bcd_e^f!gh++-4;17<yy{40GIc/de,,  f	s   D AC !3D AD $D>D  DD DD DD H9 F#?AFF#FF#FF##H9/ H4AHH4H'$H4&H''H44H9T)targetdaemonc                       e Zd Zd Zd ZddZy)#_spawn_recalc.<locals>.DummyProcessc                    || _         y r.   )pid)selfra   s     r*   __init__z,_spawn_recalc.<locals>.DummyProcess.__init__  s	    "DHr,   c                     y r.   r'   )rb   s    r*   pollz(_spawn_recalc.<locals>.DummyProcess.poll  s    r,   Nc                     y)N)r,   r,   r'   )rb   timeouts     r*   communicatez/_spawn_recalc.<locals>.DummyProcess.communicate  s    %r,   r.   )__name__
__module____qualname__rc   re   rh   r'   r,   r*   DummyProcessr_     s    # &r,   rl   z,Failed to start management command for user rK   z1Background process spawned successfully for user z (PID: )g      ?zProcess z is running successfullyz exited immediately with code: )rg   z[recalc_bazi_relations:z
] stderr:
zutf-8ignore)errorszERROR: z
] stdout:
zINFO: zTimeout waiting for process z outputz] final stderr:
z,Failed to spawn background process for user zError traceback: ),os
subprocesssyslogging	traceback	getLoggergetcwdrS   __file__pathexistsjoinrangedirnamerL   listdircurrent_cwdFileNotFoundErrorisfileenvironcopyrP   
executabledjango.core.managementr;   django.core.management.baser<   	threadingtimeThreadstartgetpidrV   rU   ra   sleepre   
returncoderh   decodeprintTimeoutExpiredkill
format_exc)r(   rp   rq   rr   rs   rt   cwd
search_diri
parent_dirmanage_path	error_msgenvr   r   r[   threadrl   processrY   stdoutstderrinfo_msgr<   r;   rZ   s   `                      @@@r*   _spawn_recalcr     sG    W56Fh iik 	j
+,7u=> ww~~bggll3<=J1XWW__Z8
+'
77>>"'',,z;"GH$CKK"Gu MN  ac}SE235VXV]V]VdVdehVibjj6Mcr6Rot5uvw'*A#F`(abb ggll34ww~~k*1+?ILL##I.. jjoo  GGL-@LL(9$% 	.sww|/D.E]SVSZSZ[gShRijk1"))+?@067A'KL)#/0&{m45)#..)9:;B7)LMK	;@2j %%-CD%QFLLN& & #299;/G 	GyPWX_XcXcWddefg 	

3 <<>!KK(7;;-/GHI
 LL8GKK=0OPWPbPbOcde@!(!4!4Q!4!?"9'+fmm\cltmNuMv wILL+GI;/0!8	V]][bks]MtLuvHKK)F8*-. 3  	LLGyPRSVWXSYRZ[\	: ,, @;GKK=PQ!(!4!4!6LL#:7)CTU[UbUbcjs{UbU|T}!~ 	@  CG9BsSTvhWX()=)=)=)?(@ABsr   CT8 G>T8 AR %A&T8 +T8 8B	R5 	R2!R--R22T8 5A;T50T8 2T8 4T55T8 8	V
AVV
c                0    t        d| dz   dz        }|dz  S )Nr   c   d   r6   )max)num_recordsblockss     r*   _get_timeout_secondsr     s"    [2%#-.FB;r,   c                  6   e Zd ZeegZegZ ee	 e
dej                  e
j                  dd       e
dej                  e
j                  dd      g ed ej                   g dd	
       ej"                  dd       ej$                  ddd       ej$                  ddd       ej&                   ed ej                   ddgd
       ej"                  d       ej"                  d       ej&                   ej&                   ej(                         dd      ddd       ej*                  dd       ej*                  dd      d      dd !       ej"                  dd"      d#       ed$d% ej(                  d&'      i      d()      d*        Zy+),PersonRelationsAPIViewpageFz'Page number for pagination (default: 1))nametypelocationrequireddescription	page_sizezANumber of results per page (default: follows PAGINATE_BY setting)PersonRelationsResponse)r@   readyrL   z7Current processing state of group relations calculation)choices	help_textz%Total number of group relations found)r   r   Tz+URL for next page of results (if available))r   
allow_nullr   z/URL for previous page of results (if available)r   r   r   uD   Relation type: 0=三合(sanhe/harmony), 1=三刑(sanxing/punishment)z3Person ID for first participant in the relationship)r   z4Person ID for second participant in the relationship   )child
min_length
max_length   zlFormation details: [["o","d","e",<earth_index>], ["p1","d","e",<earth_index>], ["p2","d","e",<earth_index>]])r   r   r   r   zeEssential person data: {name, gender, birth_date, birth_time, bazi: {yg, ye, mg, me, dg, de, hg, he}})tp1p2byp1_datap2_data)r   fieldsz0Array of group relation objects for current page)r   r   r   z?Seconds to wait before retry (only present when status="error"))statusr&   nextpreviousr%   retry_after_secondsUnauthorizedResponsedetailz-Authentication credentials were not provided.)default)   i  )r   
parameters	responsesc           
       ( |j                   (t        (j                        }t        j                  d      }|j                  d(j                   d       t        j                  j                  (d      j                  d      j                         }|t        ddd	d	d
g d      S t        j                  j                  (      j                         }t        |      }t               }|j                  j                  (j                        }|j                   xs d}	|j"                  r|j"                  j%                         nd }
|j&                  r|j&                  j%                         nd }|j(                  r|j(                  j%                         nd }|	dk(  r|
r	 t+        j,                  |
      }t1        j2                         |z
  j7                         }||kD  rd|_        t1        j2                         |_        |j9                  ddg       d}		 t;        t<        dd       xs t;        t<        dd       }|r"t?        dd(j                   d| dd |gd       |	dk(  rx|j                  d(j                   d       d|_        t1        j2                         |_        |j9                  ddg       tA        jB                  (fd       t        ddi      S |	dk(  rt        ddi      S |	dk(  r=d }|r	 t+        j,                  |      }d tE        t1        j2                         |z
  j7                               z
  }|d	k  rx|j                  d(j                   d!       d|_        t1        j2                         |_        |j9                  ddg       tA        jB                  (fd"       t        ddi      S |j                  d(j                   d#tG        d|       d$       t        dtG        d|      d%      S |j                  d(j                   d#| d$       t        d|d%      S |j                  d(j                   d'tK        tL        j                  j                  (j                  (             d)       tL        j                  j                  (j                  (      j                  d*d+      }tO               }|D ]8  }|jQ                  |jR                         |jQ                  |jT                         : i }|rt        j                  j                  |(,      }|D ]  }|jV                  |jX                  |jZ                  |j\                  d-}|j^                  r^i }d.D ]R  \  }}|j^                  j                  |i       }|s%|j                  d/      || d0<   |j                  d1      || d2<   T ||d3<   |||j                  <    g }|D ]  }|j`                  d4k(  rd	nd}||jR                  |jT                  |jb                  xs g d5} |jR                  |v r||jR                     | d6<   |jT                  |v r||jT                     | d7<   |je                  |         tK        |      }!tg               }"	 tE        |jh                  j                  d8d            }#|"jk                  |      }$tG        d	|#dz
  |$z        }%|%|$z   }&||%|& }'t        d|'|#|$|!d
d9      S # t.        $ r( t1        j2                         t5        |dz         z
  }Y dw xY w# t.        $ r Y w xY w# t.        $ r% |jI                  d(j                   d&|        Y Zw xY w# t.        $ r d}#Y w xY w):Nr4   zAPI request from user z for person relationsT)
created_byownerz-created_atr   r   r   )r   r   total)r   
paginationr%   )r   r>   idler@   )secondsrL   rA   rM   rC   TECH_CONTACTCONTACT_EMAILzBaZi group relations timeoutrE   z. group relations processing exceeded timeout (zs).)subjectmessage
from_emailrecipient_listfail_silentlyz3 state: idle -> processing, spawning background jobrB   c                 .    t         j                        S r.   r   r?   rG   s   r*   <lambda>z,PersonRelationsAPIView.get.<locals>.<lambda>i  s    -*@r,   r   i,  z< state: error -> processing (retry), spawning background jobc                 .    t         j                        S r.   r   r   s   r*   r   z,PersonRelationsAPIView.get.<locals>.<lambda>{  s    mDGG6Lr,   z state: error, retry in z seconds)r   r   z  error parsing error timestamp: z state: ready, returning )owner_user_idz
 relationsrelation_typer?   )id__inr   )r   gender
birth_date
birth_time))yyear)mmonth)dday)hhourgodgearthrY   bazisanhe)r   r   r   r   r   r   r   )r   r%   r   )6rG   r+   r?   rs   ru   rS   
BaziPersonrO   filterorder_byfirstr   r&   r   r   rP   rA   rB   r1   rJ   rM   r   fromisoformatrV   r/   r0   r   total_secondsrQ   getattrr   r   r   	on_commitintr   warninglenr   setadd
person1_id
person2_idr   r   r   r   bazi_resultr   r   appendr   query_paramsget_page_size))rb   requestkeysrZ   r   r   timeout_secondsrW   rX   r    r!   r"   r#   
started_dtelapsedto_emailretry_aftererr_dtremainrows
person_idsrpersons_datapersonspperson_databazi_simplifiedpillar_name
pillar_keypillar_datar%   r   resultr   	paginatorr   r   r   endslicedrG   s)                                           @r*   rP   zPersonRelationsAPIView.get  s   z ||477# ""#9:,TWWI5JKL %%T%BXm$UW 	
 =!'(q1E   !((//4/@FFH.{; ll$''*))3VCFCaCaS33==?gk
CFCaCaS33==?gk
?B?[?[3//99;ae L ZR%33J?
 yy{Z/>>@G(,3)/4yy{,(?A['\]&xFr'RZ\kmqJrH!$B&+DGG94bcrbssv$w'+,4:*. F?KK%y([\](4C%-2YY[C*HH$;=Y#ZH[!!"@AX|455L X|455GK%33H=F 3		f(<'K'K'M#NNF{eDGG94p$qr4@19>60GIe/fg#--.LM'<(@AAKK%y0HQPVHXX` ab#wsSTV\~$^__ KK%y(@XVWw{STT 	eDGG9$=c-BWBWB^B^mqmtmtB^Bu>v=w  xB  C  	D$$++$''+BKKO]ab U
ANN1<<(NN1<<( 
  ((//zd/SG FFhh"#,,"#,,	 ==&(O3o/Z&'mm&7&7
B&G&ALQVAWO{m1,=>ALQXAYO{m1,=>	 4p +:K'%0QTT") , )+A__/QAllllddjb	F |||+$0$>y!|||+$0$>y!NN6"   G /0		w++//:;D ++G4	AqI-.is#&
  	w  R"YY[9_q=P+QQ
R& ! @ ! NNU477)3ST\S]#^_D  	D	sQ   #[, A\  <C\0 ?A\0 %]! ,-\\ 	\-,\-0*]]!]/.]/N)ri   rj   rk   r   r   authentication_classesr   permission_classesr   _DESCr   r   INTQUERYr   r   ChoiceFieldIntegerFieldURLField	ListField	CharField	DictFieldrP   r'   r,   r*   r   r     s0   35FG)*!%%)//E  !%%)//_
" #.5k55 @"[ 6[55!&"I 1K00!&#'"O
 !5 4 4!&#'"S!
  5{44/!0%<[%<%<-.F.t&" '?k&>&>.c'" '?k&>&>.d'" '<k&;&;*?+*?*?.Ck.C.C.E3434+&
 01/0 /]	'" ,A;+@+@-2 /V," ,A;+@+@-2 /V,"3$ B "'"TG$ J ,D;+C+C!&"c,q<?@ #+3k33<klCG
%[xhy[xhr,   r   )r(   r   returnzDict[str, str])r&  rU   )r(   r   r&  None)r   r   r&  r   )5
__future__r   rs   rt   r   r   r   typingr   r   r	   django.core.cacher
   django.confr   django.core.mailr   django.utilsr/   	django.dbr   rN   r   bazi.models_groupr   rest_framework.viewsr   rest_framework.responser   rest_framework.permissionsr   rest_framework.authenticationr   'rest_framework_simplejwt.authenticationr   drf_spectacular.utilsr   r   r   r   rest_frameworkr   bazi.modelsr   r   viewsr   r  r+   r2   r   r   r   r'   r,   r*   <module>r9     sz    "   2 2 " " #   & * ! . + ( , 6 ? E b b & , -^	B
#qhHW Hr,   